├── LICENSE.txt
├── README.md
├── addon.xml
├── changelog.md
├── common.py
├── icon.png
├── lib
└── kodi18to19_xbmcgui.py
├── resources
├── language
│ └── English
│ │ └── strings.po
└── settings.xml
└── script.py
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | GNU GENERAL PUBLIC LICENSE
3 | Version 2, June 1991
4 |
5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.
6 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
7 | Everyone is permitted to copy and distribute verbatim copies
8 | of this license document, but changing it is not allowed.
9 |
10 | Preamble
11 |
12 | The licenses for most software are designed to take away your
13 | freedom to share and change it. By contrast, the GNU General Public
14 | License is intended to guarantee your freedom to share and change free
15 | software--to make sure the software is free for all its users. This
16 | General Public License applies to most of the Free Software
17 | Foundation's software and to any other program whose authors commit to
18 | using it. (Some other Free Software Foundation software is covered by
19 | the GNU Library General Public License instead.) You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | this service if you wish), that you receive source code or can get it
26 | if you want it, that you can change the software or use pieces of it
27 | in new free programs; and that you know you can do these things.
28 |
29 | To protect your rights, we need to make restrictions that forbid
30 | anyone to deny you these rights or to ask you to surrender the rights.
31 | These restrictions translate to certain responsibilities for you if you
32 | distribute copies of the software, or if you modify it.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must give the recipients all the rights that
36 | you have. You must make sure that they, too, receive or can get the
37 | source code. And you must show them these terms so they know their
38 | rights.
39 |
40 | We protect your rights with two steps: (1) copyright the software, and
41 | (2) offer you this license which gives you legal permission to copy,
42 | distribute and/or modify the software.
43 |
44 | Also, for each author's protection and ours, we want to make certain
45 | that everyone understands that there is no warranty for this free
46 | software. If the software is modified by someone else and passed on, we
47 | want its recipients to know that what they have is not the original, so
48 | that any problems introduced by others will not reflect on the original
49 | authors' reputations.
50 |
51 | Finally, any free program is threatened constantly by software
52 | patents. We wish to avoid the danger that redistributors of a free
53 | program will individually obtain patent licenses, in effect making the
54 | program proprietary. To prevent this, we have made it clear that any
55 | patent must be licensed for everyone's free use or not licensed at all.
56 |
57 | The precise terms and conditions for copying, distribution and
58 | modification follow.
59 |
60 | GNU GENERAL PUBLIC LICENSE
61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
62 |
63 | 0. This License applies to any program or other work which contains
64 | a notice placed by the copyright holder saying it may be distributed
65 | under the terms of this General Public License. The "Program", below,
66 | refers to any such program or work, and a "work based on the Program"
67 | means either the Program or any derivative work under copyright law:
68 | that is to say, a work containing the Program or a portion of it,
69 | either verbatim or with modifications and/or translated into another
70 | language. (Hereinafter, translation is included without limitation in
71 | the term "modification".) Each licensee is addressed as "you".
72 |
73 | Activities other than copying, distribution and modification are not
74 | covered by this License; they are outside its scope. The act of
75 | running the Program is not restricted, and the output from the Program
76 | is covered only if its contents constitute a work based on the
77 | Program (independent of having been made by running the Program).
78 | Whether that is true depends on what the Program does.
79 |
80 | 1. You may copy and distribute verbatim copies of the Program's
81 | source code as you receive it, in any medium, provided that you
82 | conspicuously and appropriately publish on each copy an appropriate
83 | copyright notice and disclaimer of warranty; keep intact all the
84 | notices that refer to this License and to the absence of any warranty;
85 | and give any other recipients of the Program a copy of this License
86 | along with the Program.
87 |
88 | You may charge a fee for the physical act of transferring a copy, and
89 | you may at your option offer warranty protection in exchange for a fee.
90 |
91 | 2. You may modify your copy or copies of the Program or any portion
92 | of it, thus forming a work based on the Program, and copy and
93 | distribute such modifications or work under the terms of Section 1
94 | above, provided that you also meet all of these conditions:
95 |
96 | a) You must cause the modified files to carry prominent notices
97 | stating that you changed the files and the date of any change.
98 |
99 | b) You must cause any work that you distribute or publish, that in
100 | whole or in part contains or is derived from the Program or any
101 | part thereof, to be licensed as a whole at no charge to all third
102 | parties under the terms of this License.
103 |
104 | c) If the modified program normally reads commands interactively
105 | when run, you must cause it, when started running for such
106 | interactive use in the most ordinary way, to print or display an
107 | announcement including an appropriate copyright notice and a
108 | notice that there is no warranty (or else, saying that you provide
109 | a warranty) and that users may redistribute the program under
110 | these conditions, and telling the user how to view a copy of this
111 | License. (Exception: if the Program itself is interactive but
112 | does not normally print such an announcement, your work based on
113 | the Program is not required to print an announcement.)
114 |
115 | These requirements apply to the modified work as a whole. If
116 | identifiable sections of that work are not derived from the Program,
117 | and can be reasonably considered independent and separate works in
118 | themselves, then this License, and its terms, do not apply to those
119 | sections when you distribute them as separate works. But when you
120 | distribute the same sections as part of a whole which is a work based
121 | on the Program, the distribution of the whole must be on the terms of
122 | this License, whose permissions for other licensees extend to the
123 | entire whole, and thus to each and every part regardless of who wrote it.
124 |
125 | Thus, it is not the intent of this section to claim rights or contest
126 | your rights to work written entirely by you; rather, the intent is to
127 | exercise the right to control the distribution of derivative or
128 | collective works based on the Program.
129 |
130 | In addition, mere aggregation of another work not based on the Program
131 | with the Program (or with a work based on the Program) on a volume of
132 | a storage or distribution medium does not bring the other work under
133 | the scope of this License.
134 |
135 | 3. You may copy and distribute the Program (or a work based on it,
136 | under Section 2) in object code or executable form under the terms of
137 | Sections 1 and 2 above provided that you also do one of the following:
138 |
139 | a) Accompany it with the complete corresponding machine-readable
140 | source code, which must be distributed under the terms of Sections
141 | 1 and 2 above on a medium customarily used for software interchange; or,
142 |
143 | b) Accompany it with a written offer, valid for at least three
144 | years, to give any third party, for a charge no more than your
145 | cost of physically performing source distribution, a complete
146 | machine-readable copy of the corresponding source code, to be
147 | distributed under the terms of Sections 1 and 2 above on a medium
148 | customarily used for software interchange; or,
149 |
150 | c) Accompany it with the information you received as to the offer
151 | to distribute corresponding source code. (This alternative is
152 | allowed only for noncommercial distribution and only if you
153 | received the program in object code or executable form with such
154 | an offer, in accord with Subsection b above.)
155 |
156 | The source code for a work means the preferred form of the work for
157 | making modifications to it. For an executable work, complete source
158 | code means all the source code for all modules it contains, plus any
159 | associated interface definition files, plus the scripts used to
160 | control compilation and installation of the executable. However, as a
161 | special exception, the source code distributed need not include
162 | anything that is normally distributed (in either source or binary
163 | form) with the major components (compiler, kernel, and so on) of the
164 | operating system on which the executable runs, unless that component
165 | itself accompanies the executable.
166 |
167 | If distribution of executable or object code is made by offering
168 | access to copy from a designated place, then offering equivalent
169 | access to copy the source code from the same place counts as
170 | distribution of the source code, even though third parties are not
171 | compelled to copy the source along with the object code.
172 |
173 | 4. You may not copy, modify, sublicense, or distribute the Program
174 | except as expressly provided under this License. Any attempt
175 | otherwise to copy, modify, sublicense or distribute the Program is
176 | void, and will automatically terminate your rights under this License.
177 | However, parties who have received copies, or rights, from you under
178 | this License will not have their licenses terminated so long as such
179 | parties remain in full compliance.
180 |
181 | 5. You are not required to accept this License, since you have not
182 | signed it. However, nothing else grants you permission to modify or
183 | distribute the Program or its derivative works. These actions are
184 | prohibited by law if you do not accept this License. Therefore, by
185 | modifying or distributing the Program (or any work based on the
186 | Program), you indicate your acceptance of this License to do so, and
187 | all its terms and conditions for copying, distributing or modifying
188 | the Program or works based on it.
189 |
190 | 6. Each time you redistribute the Program (or any work based on the
191 | Program), the recipient automatically receives a license from the
192 | original licensor to copy, distribute or modify the Program subject to
193 | these terms and conditions. You may not impose any further
194 | restrictions on the recipients' exercise of the rights granted herein.
195 | You are not responsible for enforcing compliance by third parties to
196 | this License.
197 |
198 | 7. If, as a consequence of a court judgment or allegation of patent
199 | infringement or for any other reason (not limited to patent issues),
200 | conditions are imposed on you (whether by court order, agreement or
201 | otherwise) that contradict the conditions of this License, they do not
202 | excuse you from the conditions of this License. If you cannot
203 | distribute so as to satisfy simultaneously your obligations under this
204 | License and any other pertinent obligations, then as a consequence you
205 | may not distribute the Program at all. For example, if a patent
206 | license would not permit royalty-free redistribution of the Program by
207 | all those who receive copies directly or indirectly through you, then
208 | the only way you could satisfy both it and this License would be to
209 | refrain entirely from distribution of the Program.
210 |
211 | If any portion of this section is held invalid or unenforceable under
212 | any particular circumstance, the balance of the section is intended to
213 | apply and the section as a whole is intended to apply in other
214 | circumstances.
215 |
216 | It is not the purpose of this section to induce you to infringe any
217 | patents or other property right claims or to contest validity of any
218 | such claims; this section has the sole purpose of protecting the
219 | integrity of the free software distribution system, which is
220 | implemented by public license practices. Many people have made
221 | generous contributions to the wide range of software distributed
222 | through that system in reliance on consistent application of that
223 | system; it is up to the author/donor to decide if he or she is willing
224 | to distribute software through any other system and a licensee cannot
225 | impose that choice.
226 |
227 | This section is intended to make thoroughly clear what is believed to
228 | be a consequence of the rest of this License.
229 |
230 | 8. If the distribution and/or use of the Program is restricted in
231 | certain countries either by patents or by copyrighted interfaces, the
232 | original copyright holder who places the Program under this License
233 | may add an explicit geographical distribution limitation excluding
234 | those countries, so that distribution is permitted only in or among
235 | countries not thus excluded. In such case, this License incorporates
236 | the limitation as if written in the body of this License.
237 |
238 | 9. The Free Software Foundation may publish revised and/or new versions
239 | of the General Public License from time to time. Such new versions will
240 | be similar in spirit to the present version, but may differ in detail to
241 | address new problems or concerns.
242 |
243 | Each version is given a distinguishing version number. If the Program
244 | specifies a version number of this License which applies to it and "any
245 | later version", you have the option of following the terms and conditions
246 | either of that version or of any later version published by the Free
247 | Software Foundation. If the Program does not specify a version number of
248 | this License, you may choose any version ever published by the Free Software
249 | Foundation.
250 |
251 | 10. If you wish to incorporate parts of the Program into other free
252 | programs whose distribution conditions are different, write to the author
253 | to ask for permission. For software which is copyrighted by the Free
254 | Software Foundation, write to the Free Software Foundation; we sometimes
255 | make exceptions for this. Our decision will be guided by the two goals
256 | of preserving the free status of all derivatives of our free software and
257 | of promoting the sharing and reuse of software generally.
258 |
259 | NO WARRANTY
260 |
261 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
262 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
263 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
264 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
265 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
266 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
267 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
268 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
269 | REPAIR OR CORRECTION.
270 |
271 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
272 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
273 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
274 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
275 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
276 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
277 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
278 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
279 | POSSIBILITY OF SUCH DAMAGES.
280 |
281 | END OF TERMS AND CONDITIONS
282 | -------------------------------------------------------------------------
283 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is an addon for Kodi 19+ designed to cleanup a user's profile (texture database, addon repository).
2 |
3 | ## Installation
4 | 1. Download the zip
5 | 2. Install script.profilecleaner by zip. Go to Settings > Add-ons > Install from zip file > Choose the just downloaded zip
6 | 3. Navigate to Settings > Add-ons > Enabled add-ons > Program add-ons > Profile Cleaner
7 | 4. Select Profile Cleaner and go to Configure if you need to change the default configuration
8 | 5. Enjoy
9 |
10 | ## Acknowledgements
11 | - Max (m4x1m) Headroom for writing the original Thumbnails Cleaner upon which this addon is based
12 | - Palindrones for updating this addon to Kodi 19 (Matrix)
13 |
--------------------------------------------------------------------------------
/addon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | executable
9 |
10 |
11 | all
12 | GNU GENERAL PUBLIC LICENSE. Version 2, June 1991
13 | http://forum.kodi.tv/showthread.php?tid=264018
14 | http://kodi.tv
15 |
16 | https://github.com/cibboy/script.profilecleaner
17 | Profile Cleaner cleans up your Kodi profile by deleting unused thumbnails, addon packages and addon settings
18 | Profile Cleaner works by exclusion: the script reads the databases and excludes images and addons found. Once completed, the remaining images, addon packages and addon settings found in your profile will be deleted - or moved, according to your preference. Based on Max (m4x1m) Headroom's Thumbnails Cleaner.
19 |
20 | icon.png
21 |
22 |
23 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | version 1.1.1
2 | - added Favorites
3 | - fixed addon's icon
4 |
5 | version 1.1.0
6 | - Matrix support
7 |
8 | version 1.0.4
9 | - Main menu visible by default upon installation
10 |
11 | version 1.0.3
12 | - xbmc.python version relaxed in order to allow automatic installation on Gotham and Helix
13 |
14 | version 1.0.2
15 | - Corrected bug that prevented proper execution on Android or other devices not using a specific python version
16 |
17 | version 1.0.1
18 | - Corrected bug that prevented proper execution during texture deletion
19 |
20 | version 1.0.0
21 | - Initial release
22 | - Texture database cleanup based on Max (m4x1m) Headroom's Thumbnails Cleaner
23 | - Addon package cleanup
24 |
--------------------------------------------------------------------------------
/common.py:
--------------------------------------------------------------------------------
1 | import xbmc, xbmcaddon, xbmcvfs
2 | import os, sqlite3, unicodedata
3 | import urllib.parse
4 | import xml.etree.ElementTree as ET
5 |
6 | addonSettings = xbmcaddon.Addon("script.profilecleaner")
7 | addonAuthor = addonSettings.getAddonInfo("author")
8 | addonName = addonSettings.getAddonInfo("name")
9 | addonVersion = addonSettings.getAddonInfo("version")
10 | addonLanguage = addonSettings.getLocalizedString
11 | addonProfile = xbmcvfs.translatePath(addonSettings.getAddonInfo("profile"))
12 | addonIcon = os.path.join(addonSettings.getAddonInfo("path"), "icon.png")
13 | addonBackup = os.path.join(addonProfile, "backup")
14 | xbmcVersion = xbmc.getInfoLabel("System.BuildVersion").split(" ")[0]
15 |
16 | if not xbmcvfs.exists(addonProfile): xbmcvfs.mkdir(addonProfile)
17 | if not xbmcvfs.exists(addonBackup): xbmcvfs.mkdir(addonBackup)
18 |
19 | databaseFolder = xbmcvfs.translatePath("special://database")
20 | thumbnailsFolder = xbmcvfs.translatePath("special://thumbnails")
21 | homeFolder = xbmcvfs.translatePath("special://home")
22 |
23 | # Read advancedsettings.xml for possible thumbnail folder redirection
24 | userdataFolder = xbmcvfs.translatePath("special://profile")
25 | if os.path.exists(os.path.join(userdataFolder, "advancedsettings.xml")):
26 | tree = ET.parse(os.path.join(userdataFolder, "advancedsettings.xml"))
27 | root = tree.getroot()
28 | for node in root.getiterator('substitute'):
29 | cfrom = node.find('from')
30 | cto = node.find('to')
31 | if cfrom != None and cto != None:
32 | if cfrom.text.lower() == "special://profile/thumbnails/":
33 | thumbnailsFolder = cto.text
34 |
35 | thumbnailBackupFolder = addonSettings.getSetting("ThumbnailDestinationFolder")
36 | if not thumbnailBackupFolder:
37 | thumbnailBackupFolder = addonBackup
38 | addonBackupFolder = addonSettings.getSetting("AddonDestinationFolder")
39 | if not addonBackupFolder:
40 | addonBackupFolder = addonBackup
41 |
42 | showGUI = (addonSettings.getSetting("ShowGui") == "true")
43 |
44 | def log(msg):
45 | xbmc.log("[%s] - %s" % (addonName, msg), xbmc.LOGINFO)
46 |
47 | def logError(msg):
48 | xbmc.log("[%s] - %s" % (addonName, msg), xbmc.LOGERROR)
49 |
50 | def _unicode(text, encoding='utf-8'):
51 | try: text = str(text, encoding)
52 | except: pass
53 | return text
54 |
55 | def normalize(text):
56 | try: text = unicodedata.normalize('NFKD', _unicode(text))#.encode('utf-8')
57 | except: pass
58 | return text
59 |
60 | def getHash(string):
61 | string = string.lower()
62 | bytes = bytearray(string, encoding='utf-8')
63 | crc = 0xffffffff;
64 | for b in bytes:
65 | crc = crc ^ (b << 24)
66 | for i in range(8):
67 | if (crc & 0x80000000): crc = (crc << 1) ^ 0x04C11DB7
68 | else: crc = crc << 1;
69 | crc = crc & 0xFFFFFFFF
70 | return '%08x' % crc
71 |
72 | def humanReadableSizeOf(size):
73 | for x in ['bytes','KB','MB','GB']:
74 | if size < 1024.0 and size > -1024.0:
75 | return "%3.1f%s" % (size, x)
76 | size /= 1024.0
77 | return "%3.1f%s" % (size, 'TB')
78 |
79 | def removeDuplicate(lists):
80 | #log(addonLanguage(32249)) #todo: review
81 | seen = set()
82 | seenAdd = seen.add
83 | return [ x for x in lists if x not in seen and not seenAdd(x) ]
84 |
85 | def cleanandAppendUrl( url:str, urllist: list ):
86 | valueText = urllib.parse.unquote_plus(normalize(url))
87 | valueText = valueText.replace("image://", "")
88 | urllist.append(valueText)
89 |
90 | class RawXBMC():
91 | @staticmethod
92 | def Query(Query):
93 | RawXBMCConnect = ConnectToXbmcDb()
94 | Cursor = RawXBMCConnect.cursor()
95 | Cursor.execute(Query)
96 | Matches = []
97 | for Row in Cursor: Matches.append(Row)
98 | RawXBMCConnect.commit()
99 | Cursor.close()
100 | return Matches
101 |
102 | @staticmethod
103 | def Execute(Query):
104 | return RawXBMC.Query(Query)
105 |
106 | def ConnectToXbmcDb():
107 | dbHost = os.path.join(databaseFolder, "Textures" + addonSettings.getSetting("TexturesDB") + ".db")
108 | return sqlite3.connect(dbHost)
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cibboy/script.profilecleaner/90391aba76f29feac3158ead9ccd2ab77f24467a/icon.png
--------------------------------------------------------------------------------
/lib/kodi18to19_xbmcgui.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 | #
3 | # Copyright (C) 2022, Palindrones
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 | '''
18 | kodi version 18 to 19 (matrix) xbmcgui wrapper classes
19 | '''
20 | import xbmcgui
21 |
22 |
23 | class Dialog(xbmcgui.Dialog):
24 |
25 | def __init__(self) -> None:
26 | pass
27 |
28 | def yesno(self, heading: str, line1: str = None, line2: str = None, line3: str = None,
29 | nolabel: str = "", yeslabel: str = "", autoclose: int = 0) -> bool:
30 | message = '\n'.join(filter(None, [line1, line2, line3]))
31 | return super().yesno(heading, message, nolabel, yeslabel, autoclose)
32 |
33 | def ok(self, heading: str, line1: str = None, line2: str = None, line3: str = None) -> bool:
34 | message = '\n'.join(filter(None, [line1, line2, line3]))
35 | return super().ok(heading, message)
36 |
37 |
38 | class DialogProgress(xbmcgui.DialogProgress):
39 |
40 | def __init__(self) -> None:
41 | pass
42 |
43 | def create(self, heading: str, line1: str = None, line2: str = None, line3: str = None) -> bool:
44 | message = '\n'.join(filter(None, [line1, line2, line3]))
45 | return super().create(heading, message)
46 |
47 | def update(self, percent: any, line1: str = None, line2: str = None, line3: str = None) -> bool:
48 | message = '\n'.join(filter(None, [line1, line2, line3]))
49 | if not isinstance(percent, int):
50 | percent = int(percent)
51 | return super().update(percent, message)
52 |
53 |
54 | if (__name__ == "__main__"):
55 | Dialog().ok("heading", "line1")
56 | Dialog().ok("heading", "line1", "")
57 | Dialog().ok("heading", "line1", "line2", None)
58 |
59 | response = Dialog().yesno("heading", "line1")
60 | response = Dialog().yesno("heading", "line1", "")
61 | response = Dialog().yesno("heading", "line1", "line2", None)
62 |
63 | DialogProgress().create("heading", "line1")
64 | DialogProgress().create("heading", "line1", "")
65 | DialogProgress().create("heading", "line1", "line2", None)
66 |
67 | progress = (1 * 100) / 121
68 | DialogProgress().update(progress, "line1")
69 | DialogProgress().update(progress, "line1", "")
70 | DialogProgress().update(progress, "line1", "line2", None)
71 |
--------------------------------------------------------------------------------
/resources/language/English/strings.po:
--------------------------------------------------------------------------------
1 | # Kodi Media Center Language File
2 | # Addon Name: Profile Cleaner
3 | # Addon ID: script.profilecleaner
4 | # Addon Provider: zeppy
5 | msgid ""
6 | msgstr ""
7 | "Project-Id-Version: Profile Cleaner\n"
8 | "Report-Msgid-Bugs-To: alanwww1@Kodi.org\n"
9 | "POT-Creation-Date: 2014-10-26 17:05+0000\n"
10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 | "Last-Translator: FULL NAME \n"
12 | "Language-Team: LANGUAGE\n"
13 | "MIME-Version: 1.0\n"
14 | "Content-Type: text/plain; charset=UTF-8\n"
15 | "Content-Transfer-Encoding: 8bit\n"
16 | "Language: en\n"
17 | "Plural-Forms: nplurals=2; plural=(n != 1)\n"
18 |
19 | msgctxt "Addon Summary"
20 | msgid "Profile Cleaner cleans up your Kodi profile by deleting unused thumbnails, addon packages and addon settings"
21 | msgstr ""
22 |
23 | msgctxt "Addon Description"
24 | msgid "Profile Cleaner works by exclusion: the script reads the databases and excludes images and addons found. Once completed, the remaining images, addon packages and addon settings found in your profile will be deleted - or moved, according to your preference."
25 | msgstr ""
26 |
27 | # Settings
28 | msgctxt "#32000"
29 | msgid "Thumbnail cleanup"
30 | msgstr ""
31 |
32 | msgctxt "#32001"
33 | msgid "Addon cleanup"
34 | msgstr ""
35 |
36 | msgctxt "#32002"
37 | msgid "Advanced"
38 | msgstr ""
39 |
40 | msgctxt "#32020"
41 | msgid "Cleanup action"
42 | msgstr ""
43 |
44 | msgctxt "#32021"
45 | msgid "Select destination folder"
46 | msgstr ""
47 |
48 | msgctxt "#32022"
49 | msgid "Enter version number of Textures database"
50 | msgstr ""
51 |
52 | msgctxt "#32023"
53 | msgid "Type an extra pattern to keep specific elements"
54 | msgstr ""
55 |
56 | msgctxt "#32024"
57 | msgid "Delete addon settings for uninstalled addons"
58 | msgstr ""
59 |
60 | msgctxt "#32025"
61 | msgid "Delete addon packages for uninstalled addons"
62 | msgstr ""
63 |
64 | msgctxt "#32026"
65 | msgid "Limit number of addon packages to keep"
66 | msgstr ""
67 |
68 | msgctxt "#32027"
69 | msgid "Number of addon packages to keep"
70 | msgstr ""
71 |
72 | msgctxt "#32028"
73 | msgid "General"
74 | msgstr ""
75 |
76 | msgctxt "#32029"
77 | msgid "Show GUI"
78 | msgstr ""
79 |
80 | msgctxt "#32030"
81 | msgid "Show main menu"
82 | msgstr ""
83 |
84 | msgctxt "#32031"
85 | msgid "Show notifications"
86 | msgstr ""
87 |
88 | msgctxt "#32032"
89 | msgid "Thumbnails"
90 | msgstr ""
91 |
92 | msgctxt "#32033"
93 | msgid "Clean Movies"
94 | msgstr ""
95 |
96 | msgctxt "#32034"
97 | msgid "Clean Sets"
98 | msgstr ""
99 |
100 | msgctxt "#32035"
101 | msgid "Clean TV Shows"
102 | msgstr ""
103 |
104 | msgctxt "#32036"
105 | msgid "Clean Seasons"
106 | msgstr ""
107 |
108 | msgctxt "#32037"
109 | msgid "Clean Episodes"
110 | msgstr ""
111 |
112 | msgctxt "#32038"
113 | msgid "Clean Music Videos"
114 | msgstr ""
115 |
116 | msgctxt "#32039"
117 | msgid "Clean Video Genres"
118 | msgstr ""
119 |
120 | msgctxt "#32040"
121 | msgid "Clean Music Artists"
122 | msgstr ""
123 |
124 | msgctxt "#32041"
125 | msgid "Clean Music Albums"
126 | msgstr ""
127 |
128 | msgctxt "#32042"
129 | msgid "Clean Music Songs"
130 | msgstr ""
131 |
132 | msgctxt "#32043"
133 | msgid "Clean Music Genres"
134 | msgstr ""
135 |
136 | msgctxt "#32044"
137 | msgid "Clean Actors"
138 | msgstr ""
139 |
140 | msgctxt "#32045"
141 | msgid "Clean Addons"
142 | msgstr ""
143 |
144 | msgctxt "#32046"
145 | msgid "Clean Favorites"
146 | msgstr ""
147 |
148 | msgctxt "#32090"
149 | msgid "Move"
150 | msgstr ""
151 |
152 | msgctxt "#32091"
153 | msgid "Delete"
154 | msgstr ""
155 |
156 | msgctxt "#32092"
157 | msgid "Simulate"
158 | msgstr ""
159 |
160 |
161 | # Dialogs
162 | msgctxt "#32100"
163 | msgid "Thumbnail Cleanup"
164 | msgstr ""
165 |
166 | msgctxt "#32101"
167 | msgid "Search and Delete Thumbnails"
168 | msgstr ""
169 |
170 | msgctxt "#32102"
171 | msgid "Delete All Thumbnails"
172 | msgstr ""
173 |
174 | msgctxt "#32103"
175 | msgid "Addon Cleanup"
176 | msgstr ""
177 |
178 | msgctxt "#32104"
179 | msgid "Show Statistics"
180 | msgstr ""
181 |
182 | msgctxt "#32105"
183 | msgid "Settings"
184 | msgstr ""
185 |
186 | msgctxt "#32106"
187 | msgid "Statistics"
188 | msgstr ""
189 |
190 | msgctxt "#32107"
191 | msgid "Files"
192 | msgstr ""
193 |
194 | msgctxt "#32108"
195 | msgid "Textures"
196 | msgstr ""
197 |
198 | msgctxt "#32109"
199 | msgid "Addons"
200 | msgstr ""
201 |
202 | msgctxt "#32110"
203 | msgid "Found %s images to process"
204 | msgstr ""
205 |
206 | msgctxt "#32111"
207 | msgid "Reading data for movies"
208 | msgstr ""
209 |
210 | msgctxt "#32112"
211 | msgid "Comparing images for movies to exclude files"
212 | msgstr ""
213 |
214 | msgctxt "#32113"
215 | msgid "Reading data for collections"
216 | msgstr ""
217 |
218 | msgctxt "#32114"
219 | msgid "Comparing images for collections to exclude files"
220 | msgstr ""
221 |
222 | msgctxt "#32115"
223 | msgid "Reading data for tv shows"
224 | msgstr ""
225 |
226 | msgctxt "#32116"
227 | msgid "Comparing images for tv shows to exclude files"
228 | msgstr ""
229 |
230 | msgctxt "#32117"
231 | msgid "Reading data for seasons"
232 | msgstr ""
233 |
234 | msgctxt "#32118"
235 | msgid "Comparing images for seasons to exclude files"
236 | msgstr ""
237 |
238 | msgctxt "#32119"
239 | msgid "Reading data for episodes"
240 | msgstr ""
241 |
242 | msgctxt "#32120"
243 | msgid "Comparing images for episodes to exclude files"
244 | msgstr ""
245 |
246 | msgctxt "#32121"
247 | msgid "Reading data for music videos"
248 | msgstr ""
249 |
250 | msgctxt "#32122"
251 | msgid "Comparing images for music videos to exclude files"
252 | msgstr ""
253 |
254 | msgctxt "#32123"
255 | msgid "Reading data for movie genres"
256 | msgstr ""
257 |
258 | msgctxt "#32124"
259 | msgid "Reading data for tv show genres"
260 | msgstr ""
261 |
262 | msgctxt "#32125"
263 | msgid "Reading data for music video genres"
264 | msgstr ""
265 |
266 | msgctxt "#32126"
267 | msgid "Comparing images for video genres to exclude files"
268 | msgstr ""
269 |
270 | msgctxt "#32127"
271 | msgid "Reading data for artists"
272 | msgstr ""
273 |
274 | msgctxt "#32128"
275 | msgid "Comparing images for artists to exclude files"
276 | msgstr ""
277 |
278 | msgctxt "#32129"
279 | msgid "Reading data for albums"
280 | msgstr ""
281 |
282 | msgctxt "#32130"
283 | msgid "Comparing images for albums to exclude files"
284 | msgstr ""
285 |
286 | msgctxt "#32131"
287 | msgid "Reading data for songs"
288 | msgstr ""
289 |
290 | msgctxt "#32132"
291 | msgid "Comparing images for songs to exclude files"
292 | msgstr ""
293 |
294 | msgctxt "#32133"
295 | msgid "Reading data for music genres"
296 | msgstr ""
297 |
298 | msgctxt "#32134"
299 | msgid "Comparing images for music genres to exclude files"
300 | msgstr ""
301 |
302 | msgctxt "#32135"
303 | msgid "Reading data for actors in movies"
304 | msgstr ""
305 |
306 | msgctxt "#32136"
307 | msgid "Reading data for actors in tv shows"
308 | msgstr ""
309 |
310 | msgctxt "#32137"
311 | msgid "Reading data for actors in episodes"
312 | msgstr ""
313 |
314 | msgctxt "#32138"
315 | msgid "Comparing images for actors to exclude files"
316 | msgstr ""
317 |
318 | msgctxt "#32139"
319 | msgid "Reading data for addons"
320 | msgstr ""
321 |
322 | msgctxt "#32140"
323 | msgid "Comparing images for addons to exclude files"
324 | msgstr ""
325 |
326 | msgctxt "#32141"
327 | msgid "Deleting %s remaining textures from the database"
328 | msgstr ""
329 |
330 | msgctxt "#32142"
331 | msgid "Deleting %s images"
332 | msgstr ""
333 |
334 | msgctxt "#32143"
335 | msgid "Moving %s images"
336 | msgstr ""
337 |
338 | msgctxt "#32144"
339 | msgid "Copying %s images"
340 | msgstr ""
341 |
342 | msgctxt "#32145"
343 | msgid "Texture cleanup complete"
344 | msgstr ""
345 |
346 | msgctxt "#32146"
347 | msgid "%s file(s) deleted. %s recovered."
348 | msgstr ""
349 |
350 | msgctxt "#32147"
351 | msgid "%s field(s) deleted from the textures database."
352 | msgstr ""
353 |
354 | msgctxt "#32148"
355 | msgid "%s file(s) moved. %s recovered."
356 | msgstr ""
357 |
358 | msgctxt "#32149"
359 | msgid "%s file(s) copied. %s recoverable."
360 | msgstr ""
361 |
362 | msgctxt "#32150"
363 | msgid "%s field(s) would be deleted from the textures database."
364 | msgstr ""
365 |
366 | msgctxt "#32151"
367 | msgid "Select what to delete"
368 | msgstr ""
369 |
370 | msgctxt "#32152"
371 | msgid "No results found!"
372 | msgstr ""
373 |
374 | msgctxt "#32153"
375 | msgid "Are you sure to want empty the textures database?"
376 | msgstr ""
377 |
378 | msgctxt "#32154"
379 | msgid "A copy of the texture database will be created before continuing"
380 | msgstr ""
381 |
382 | msgctxt "#32155"
383 | msgid "A copy of the texture database has been created"
384 | msgstr ""
385 |
386 | msgctxt "#32156"
387 | msgid "The texture database has been emptied"
388 | msgstr ""
389 |
390 | msgctxt "#32157"
391 | msgid "Deleting settings for "
392 | msgstr ""
393 |
394 | msgctxt "#32158"
395 | msgid "Moving settings for "
396 | msgstr ""
397 |
398 | msgctxt "#32159"
399 | msgid "Copying settings for "
400 | msgstr ""
401 |
402 | msgctxt "#32160"
403 | msgid "Deleting package "
404 | msgstr ""
405 |
406 | msgctxt "#32161"
407 | msgid "Moving package "
408 | msgstr ""
409 |
410 | msgctxt "#32162"
411 | msgid "Copying package "
412 | msgstr ""
413 |
414 | msgctxt "#32163"
415 | msgid "Addon cleanup complete"
416 | msgstr ""
417 |
418 | msgctxt "#32164"
419 | msgid "Cleanup started"
420 | msgstr ""
421 |
422 | msgctxt "#32165"
423 | msgid "Cleanup complete"
424 | msgstr ""
425 |
426 | msgctxt "#32166"
427 | msgid "Reading data for Favorites"
428 | msgstr ""
429 |
430 | msgctxt "#32167"
431 | msgid "Comparing images for Favorites to exclude files"
432 | msgstr ""
--------------------------------------------------------------------------------
/resources/settings.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/script.py:
--------------------------------------------------------------------------------
1 | import traceback
2 | import xbmc
3 | import lib.kodi18to19_xbmcgui as xbmcgui
4 | import os, sys, urllib.request, urllib.parse, urllib.error, datetime, time, shutil, re
5 | if sys.version_info >= (2, 7): import json
6 | else: import simplejson as json
7 | from common import *
8 |
9 | class Cleaner:
10 | def __init__(self):
11 | self.thumbnailFileList = [] # All thumbnails
12 | self.texturesDict = {} # All textures (from DB)
13 | self.thumbnailFileSize = 0 # Initial thumbnail file size
14 | self.cancelOperation = False # Init cancelOperation Flag
15 |
16 | def ExploreThumbnailsFolder(self, thumbnailsFolder):
17 | basedir = thumbnailsFolder; subdirlist = []
18 | for item in os.listdir(thumbnailsFolder):
19 | if os.path.isfile(os.path.join(basedir, item)):
20 | last = os.path.split(os.path.dirname(basedir))[1]
21 | itemWithDir = basedir + "/" + item
22 | self.thumbnailFileList.append((item))
23 | self.thumbnailFileSize = self.thumbnailFileSize + os.stat(os.path.join(basedir, item)).st_size
24 | else: subdirlist.append(os.path.join(basedir, item))
25 | for subdir in subdirlist:
26 | self.ExploreThumbnailsFolder(subdir)
27 |
28 | def ShowStats(self, showAddons = False):
29 | # Get all Files in Thumbnails folder and all Textures in Database
30 | self.ExploreThumbnailsFolder(thumbnailsFolder)
31 | jSonQuery = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Textures.GetTextures", "id": 1}')
32 | jSon = json.loads(jSonQuery)
33 | try:
34 | if 'textures' in jSon['result']: getTextures = jSon['result']['textures']
35 | except: getTextures = []
36 |
37 | statsT = normalize(addonLanguage(32108)) + ": " + humanReadableSizeOf(self.thumbnailFileSize) + ", " + str(len(self.thumbnailFileList)) + " " + normalize(addonLanguage(32107)) + ", " + str(len(getTextures)) + " " + normalize(addonLanguage(32108))
38 | statsA = ""
39 |
40 | if showAddons:
41 | # Compute addon size and number of files.
42 | totalAddonSize = 0
43 | totalAddonFiles = 0
44 | addonData = os.path.join(userdataFolder, "addon_data")
45 | for item in os.listdir(addonData):
46 | totalAddonSize = totalAddonSize + self.GetFolderSize(os.path.join(addonData, item))
47 | totalAddonFiles = totalAddonFiles + 1
48 | addonRollback = os.path.join(homeFolder, "addons", "packages")
49 | for item in os.listdir(addonRollback):
50 | totalAddonSize = totalAddonSize + os.stat(os.path.join(addonRollback, item)).st_size
51 | totalAddonFiles = totalAddonFiles + 1
52 |
53 | statsA = normalize(addonLanguage(32109)) + ": " + humanReadableSizeOf(totalAddonSize) + ", " + str(totalAddonFiles) + " " + normalize(addonLanguage(32107))
54 |
55 | # Show stats
56 | xbmcgui.Dialog().ok(addonName + " - " + normalize(addonLanguage(32106)), statsT, statsA)
57 |
58 | def ExcludeThumbnailHash(self, sectionToDo, textToDo):
59 | countList = 1
60 | excludedTextureCount = 0
61 | tnHashFileList = []
62 |
63 | log("Comparing " + str(len(sectionToDo)) + " images to exclude texture entries")
64 | for s in sectionToDo:
65 | if showGUI:
66 | self.Progress.update((countList * 100) / len(sectionToDo), normalize(addonLanguage(32110)) % len(sectionToDo), textToDo, s)
67 | if self.Progress.iscanceled():
68 | self.cancelOperation = True
69 | break
70 | #calcualte hash filenames
71 | ts = s.rstrip('/')
72 | urlHash = getHash(ts)
73 | extFile = ts.split(".")[-1]
74 | tnHashFileList.append(urlHash + "." + extFile)
75 | if "icon.png" in s: # Workaround for the icons addon
76 | tnHashFileList.append(urlHash + ".jpg")
77 |
78 | # remove from texturelist and add to hashfilelist
79 | matchingTextuture = next((item for key,item in self.texturesDict.items() if item['url'] == s), None)
80 | if matchingTextuture:
81 | tnHashFileList.append(matchingTextuture['cachedurl'])
82 | del self.texturesDict[matchingTextuture['textureid']]
83 | excludedTextureCount = excludedTextureCount + 1
84 | countList = countList + 1
85 |
86 | # remove from filelist
87 | tnHashFileList = removeDuplicate(tnHashFileList) #remove duplicates
88 | log("Comparing " + str(len(tnHashFileList)) + " updated images to exclude files")
89 | excludedThumbFileCount = 0
90 | for s in tnHashFileList:
91 | if showGUI:
92 | self.Progress.update((excludedThumbFileCount * 100) / len(tnHashFileList), normalize(addonLanguage(32110)) % len(tnHashFileList), textToDo)
93 | if self.Progress.iscanceled():
94 | self.cancelOperation = True
95 | break
96 | try:
97 | self.thumbnailFileList.remove(s)
98 | excludedThumbFileCount += 1
99 | except:
100 | pass
101 | xbmc.log(str(excludedThumbFileCount) + " image files excluded")
102 | xbmc.log(str(excludedTextureCount) + " texture image entry excluded")
103 |
104 | def _processMediaThumbnails(self, mediaType: str, jsonrpccommand: str, languageId: int, excludeHashLanguageId: int, dialogExtension: str = '') -> None:
105 | if self.cancelOperation != True:
106 | thumbnailsList = []; countList = 1
107 | log("*** Searching %s %s ***" %(dialogExtension, mediaType))
108 | try:
109 | # Get all Textures in Database
110 | jSonQuery = xbmc.executeJSONRPC(jsonrpccommand)
111 | jSon = json.loads(jSonQuery)
112 |
113 | if jSon['result'].get(mediaType):
114 | totalResults = jSon['result']['limits'].get('total')
115 | log("Found %d %s in the database" % (totalResults, mediaType))
116 | for item in jSon['result'][mediaType]:
117 | if showGUI:
118 | self.Progress.update((countList * 100) / totalResults,
119 | normalize(addonLanguage(languageId)), dialogExtension, item.get('label'))
120 | if self.Progress.iscanceled():
121 | self.cancelOperation = True
122 | break
123 | #time.sleep(0.0005)
124 | #collections
125 | if item.get('art'):
126 | for artKey, artValue in item['art'].items(): #for json art dictionary objects in movies/tvshows/episodes
127 | if artKey.startswith('tvshow.') or 'DefaultVideo.png' in artValue: continue # check for bad season/episode entries *performance
128 | cleanandAppendUrl(artValue, thumbnailsList)
129 | if item['label'] == "Season 1": # Workaround for tv show seasons all posters
130 | cleanandAppendUrl(artValue.replace("season01", "season-all"), thumbnailsList)
131 | if item.get('cast'):
132 | for cast in item['cast']: #for actors in movies/tvshows/episodes
133 | if cast.get('thumbnail'):
134 | cleanandAppendUrl(cast['thumbnail'], thumbnailsList)
135 | #individuals
136 | if item.get('thumbnail'): #for json thumbnail objects // conditional fails on missing key or emtpy string or None value
137 | cleanandAppendUrl(item['thumbnail'], thumbnailsList)
138 | if item.get('fanart'): #for json thumbnail objects // conditional fails on missing key or emtpy string or None value
139 | cleanandAppendUrl(item['fanart'], thumbnailsList)
140 | countList = countList + 1
141 | if not self.cancelOperation and len(thumbnailsList):
142 | thumbnailsList = removeDuplicate(thumbnailsList)
143 | self.ExcludeThumbnailHash(thumbnailsList, normalize(
144 | addonLanguage(excludeHashLanguageId)))
145 | else:
146 | log("Found 0 images to process")
147 | except Exception as e:
148 | logError(mediaType + " check exception: " + traceback.format_exc())
149 |
150 | def ThumbnailCleanup(self):
151 | self.startedAt = datetime.datetime.now()
152 | if addonSettings.getSetting("ThumbnailSelectDeleteMove") == "2": log("Thumbnail deletion simulation started at " + self.startedAt.strftime("%Y-%m-%d %H:%M:%d"))
153 | else: log("Thumbnail cleanup started at " + self.startedAt.strftime("%Y-%m-%d %H:%M:%d"))
154 | # Get all Files in Thumbnails folder
155 | self.ExploreThumbnailsFolder(thumbnailsFolder)
156 | self.numThumbnailFiles = len(self.thumbnailFileList)
157 | # Get all Textures in Database
158 | jSonQuery = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Textures.GetTextures", "params": {"properties": ["url","cachedurl"]}, "id": 1}')
159 | jSon = json.loads(jSonQuery)
160 | try:
161 | if 'textures' in jSon['result']: getTextures = jSon['result']['textures']
162 | for item in getTextures:
163 | #if item['url'] and item['cachedurl']: clean list?
164 | item['url'] = urllib.parse.unquote_plus(normalize(item.get('url'))).replace("image://", "")
165 | item['cachedurl'] = item.get('cachedurl').split("/")[1]
166 | self.texturesDict[item['textureid'] ] = item
167 | except Exception as e:
168 | logError('Failed to load textures from database' + traceback.format_exc())
169 | self.numTextures = len(self.texturesDict)
170 |
171 | # Movies
172 | if addonSettings.getSetting("CheckMovies") == "true":
173 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": {"properties": ["art"], "sort": {"order": "ascending", "method": "title", "ignorearticle": true}}, "id": 1}'
174 | self._processMediaThumbnails('movies', jsonrpccommand, 32111, 32112)
175 |
176 | # Sets
177 | if addonSettings.getSetting("CheckSets") == "true":
178 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovieSets", "params": {"properties": ["art"], "sort": {"order": "ascending", "method": "title", "ignorearticle": true}}, "id": 1}'
179 | self._processMediaThumbnails('sets', jsonrpccommand, 32113, 32114)
180 |
181 | # TVShows
182 | if addonSettings.getSetting("CheckTVShows") == "true":
183 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": {"properties": ["art"], "sort": {"order": "ascending", "method": "title", "ignorearticle": true}}, "id": 1}'
184 | self._processMediaThumbnails('tvshows', jsonrpccommand, 32115, 32116)
185 | #get tv show list
186 | tvShows = []
187 | jSonQuery = xbmc.executeJSONRPC(jsonrpccommand)
188 | jSon = json.loads(jSonQuery)
189 | for tvshow in jSon['result']['tvshows']:
190 | tvShows.append((tvshow['tvshowid'], tvshow['label']))
191 |
192 | # Seasons
193 | if addonSettings.getSetting("CheckSeasons") == "true":
194 | for tvShow in tvShows:
195 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetSeasons", "params": {"properties": ["art"], "tvshowid": %s}, "id": 1}' % tvShow[0]
196 | self._processMediaThumbnails('seasons', jsonrpccommand, 32117, 32118, tvShow[1])
197 |
198 | # Episodes
199 | if addonSettings.getSetting("CheckEpisodes") == "true":
200 | for tvShow in tvShows:
201 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": {"properties": ["art"], "tvshowid": %s}, "id": 1}' % tvShow[0]
202 | self._processMediaThumbnails('episodes', jsonrpccommand, 32119, 32120, tvShow[1])
203 |
204 | # MusicVideos
205 | if addonSettings.getSetting("CheckMusicVideos") == "true":
206 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetMusicVideos", "params": {"properties": ["art"], "sort": {"order": "ascending", "method": "title", "ignorearticle": true}}, "id": 1}'
207 | self._processMediaThumbnails('musicvideos', jsonrpccommand, 32121, 32122)
208 |
209 | # Video Genres
210 | if addonSettings.getSetting("CheckVideoGenres") == "true":
211 | log("*** Searching video/music genres ***")
212 | # Movies
213 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetGenres", "params": {"sort": {"order": "ascending", "method": "label", "ignorearticle": true}, "properties": ["thumbnail"], "type": "movie"}, "id": 1}'
214 | self._processMediaThumbnails('genres', jsonrpccommand, 32123, 32126)
215 | # TV Shows
216 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetGenres", "params": {"sort": {"order": "ascending", "method": "label", "ignorearticle": true}, "properties": ["thumbnail"], "type": "tvshow"}, "id": 1}'
217 | self._processMediaThumbnails('genres', jsonrpccommand, 32124, 32126)
218 | # Music Videos
219 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetGenres", "params": {"sort": {"order": "ascending", "method": "label", "ignorearticle": true}, "properties": ["thumbnail"], "type": "musicvideo"}, "id": 1}'
220 | self._processMediaThumbnails('genres', jsonrpccommand, 32125, 32126)
221 |
222 | # Music Artists
223 | if addonSettings.getSetting("CheckMusicArtists") == "true":
224 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "AudioLibrary.GetArtists", "params": {"properties": ["thumbnail","fanart"], "sort": {"order": "ascending", "method": "title", "ignorearticle": true}}, "id": 1}'
225 | self._processMediaThumbnails('artists', jsonrpccommand, 32127, 32128)
226 |
227 | # Music Albums
228 | if addonSettings.getSetting("CheckMusicAlbums") == "true":
229 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "AudioLibrary.GetAlbums", "params": {"properties": ["thumbnail","fanart"], "sort": {"order": "ascending", "method": "title", "ignorearticle": true}}, "id": 1}'
230 | self._processMediaThumbnails('albums', jsonrpccommand, 32129, 32130)
231 |
232 | # Music Songs
233 | if addonSettings.getSetting("CheckMusicSongs") == "true":
234 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "AudioLibrary.GetSongs", "params": {"properties": ["thumbnail","fanart"], "sort": {"order": "ascending", "method": "title", "ignorearticle": true}}, "id": 1}'
235 | self._processMediaThumbnails('songs', jsonrpccommand, 32131, 32132)
236 |
237 | # Music Genres
238 | if addonSettings.getSetting("CheckMusicGenres") == "true":
239 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "AudioLibrary.GetGenres", "params": {"sort": {"order": "ascending", "method": "title", "ignorearticle": true}, "properties": ["thumbnail"]}, "id": 1}'
240 | self._processMediaThumbnails('genres', jsonrpccommand, 32133, 32134)
241 |
242 | # Actors
243 | if addonSettings.getSetting("CheckActors") == "true":
244 | log("*** Searching Actors ***")
245 | # Movies
246 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": {"properties": ["cast"], "sort": {"order": "ascending", "method": "title", "ignorearticle": true}}, "id": 1}'
247 | self._processMediaThumbnails('movies', jsonrpccommand, 32135, 32138)
248 | # TV Shows
249 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": {"properties": ["cast"], "sort": {"order": "ascending", "method": "title", "ignorearticle": true}}, "id": 1}'
250 | self._processMediaThumbnails('tvshows', jsonrpccommand, 32136, 32138)
251 | # Episodes
252 | for tvShow in tvShows:
253 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": {"properties": ["cast"], "tvshowid": %s}, "id": 1}' % tvShow[0]
254 | self._processMediaThumbnails('episodes', jsonrpccommand, 32137, 32138, tvShow[1])
255 |
256 | # Addons
257 | if addonSettings.getSetting("CheckAddons") == "true":
258 | jsonrpccommand = '{"jsonrpc": "2.0", "method": "Addons.GetAddons", "params": {"properties": ["thumbnail","fanart"]}, "id": 1}'
259 | self._processMediaThumbnails('addons', jsonrpccommand, 32139, 32140) #note: item.get('addonid')
260 |
261 | # Favorites
262 | if addonSettings.getSetting("CheckFavorites") == "true":
263 | jsonrpccommand = '{"jsonrpc":"2.0","method":"Favourites.GetFavourites","params":{"properties":["thumbnail"]},"id":1}'
264 | self._processMediaThumbnails('favourites', jsonrpccommand, 32166, 32167) #note: item.get('title')
265 |
266 | # Process Textures Database
267 | if self.cancelOperation != True:
268 | newTexturesDict = {}
269 | ExtraPattern = addonSettings.getSetting("ExtraPattern").split("|")
270 | ExtraPattern.extend(['DefaultVideo.png', 'DefaultFolder.png']) #add default values
271 | for key, t in self.texturesDict.items():
272 | if not any(x in t['url'] for x in ExtraPattern): newTexturesDict[key] = t #dectect entries not matching ExtraPatterns
273 | else: #remove matching entries from TN filelist
274 | try:
275 | self.thumbnailFileList.remove(t['cachedurl'])
276 | except: pass
277 | self.texturesDict = newTexturesDict
278 |
279 | # To the End
280 | if self.cancelOperation != True:
281 | self.originalNumThumbnailFiles = self.numThumbnailFiles
282 | self.originalThumbnailFileSize = self.thumbnailFileSize
283 | self.numTextures = self.numTextures - len(self.texturesDict)
284 | # Re-Calculate Total Files and Sizes
285 | self.numThumbnailFiles = self.numThumbnailFiles - len(self.thumbnailFileList)
286 | self.newThumbnailFileSize = 0
287 | for f in self.thumbnailFileList:
288 | files = os.path.join(thumbnailsFolder, f[:1], f)
289 | try: self.newThumbnailFileSize = self.newThumbnailFileSize + os.stat(files).st_size
290 | except: pass
291 | self.thumbnailFileSize = self.thumbnailFileSize - self.newThumbnailFileSize
292 | # Finalize Clean
293 | self.FinalizeThumbnailCleanup()
294 | else:
295 | log("The operation was aborted by the user")
296 |
297 | def FinalizeThumbnailCleanup(self):
298 | texturesLength = len(self.texturesDict)
299 | thumbnailFilesLength = len(self.thumbnailFileList)
300 | # Move files in destination folder or copy if simulate is active
301 | if addonSettings.getSetting("ThumbnailSelectDeleteMove") != "2":
302 | countList = 1
303 | log("Deleting " + str(texturesLength) + " remaining textures from the database")
304 | for tKey,t in self.texturesDict.items():
305 | if showGUI:
306 | self.Progress.update((countList * 100) / texturesLength, normalize(addonLanguage(32141)) % texturesLength, t['url'])
307 | RawXBMC.Execute("DELETE FROM texture WHERE id=" + str(tKey))
308 | countList = countList + 1
309 | countList = 1
310 | for f in self.thumbnailFileList:
311 | files = os.path.join(thumbnailsFolder, f[:1], f)
312 | progress = int((countList * 100) / thumbnailFilesLength)
313 | if addonSettings.getSetting("ThumbnailSelectDeleteMove") == "1":
314 | if showGUI:
315 | self.Progress.update(progress, normalize(addonLanguage(32141)) % thumbnailFilesLength, files)
316 | try: os.remove(files)
317 | #except: pass
318 | except Exception as e:
319 | logError("Deleting File exception: " +str(files)+" " + traceback.format_exc())
320 | elif addonSettings.getSetting("ThumbnailSelectDeleteMove") == "0":
321 | if showGUI:
322 | self.Progress.update(progress, normalize(addonLanguage(32143)) % thumbnailFilesLength, files)
323 | try: os.remove(os.path.join(thumbnailBackupFolder, f))
324 | except: pass
325 | try: shutil.move(files, thumbnailBackupFolder)
326 | #except: pass
327 | except Exception as e:
328 | logError("Moving File exception: "+ str(files)+" " + traceback.format_exc())
329 | elif addonSettings.getSetting("ThumbnailSelectDeleteMove") == "2":
330 | if showGUI:
331 | self.Progress.update(progress, normalize(addonLanguage(32144)) % thumbnailFilesLength, files)
332 | try: shutil.copy2(files, thumbnailBackupFolder)
333 | #except: pass
334 | except Exception as e:
335 | logError("Copying File exception: "+str(files)+" " + traceback.format_exc())
336 | countList = countList + 1
337 |
338 | self.finishAt = datetime.datetime.now()
339 | self.tookTime = self.finishAt - self.startedAt
340 | log("Thumbnail cleanup terminated in " + str(self.tookTime).split(".")[0])
341 |
342 | # Manage Textual Response
343 | # Delete
344 | dialogHeading = addonName + " - " + normalize(addonLanguage(32145))
345 | if addonSettings.getSetting("ThumbnailSelectDeleteMove") == "1":
346 | log(str(thumbnailFilesLength) + " file(s) deleted. " + humanReadableSizeOf(self.newThumbnailFileSize) + " recovered")
347 | log(str(texturesLength) + " field(s) deleted from the textures database")
348 | if showGUI:
349 | xbmcgui.Dialog().ok(addonName + " - " + normalize(addonLanguage(32145)), normalize(addonLanguage(32146)) % (str(thumbnailFilesLength), humanReadableSizeOf(self.newThumbnailFileSize)), normalize(addonLanguage(32147)) % str(texturesLength))
350 | # Move
351 | elif addonSettings.getSetting("ThumbnailSelectDeleteMove") == "0":
352 | log(str(thumbnailFilesLength) + " file(s) moved. " + humanReadableSizeOf(self.newThumbnailFileSize) + " recovered")
353 | log(str(texturesLength) + " field(s) deleted from the textures database")
354 | if showGUI:
355 | xbmcgui.Dialog().ok(addonName + " - " + normalize(addonLanguage(32145)), normalize(addonLanguage(32148)) % (str(thumbnailFilesLength), humanReadableSizeOf(self.newThumbnailFileSize)), normalize(addonLanguage(32147)) % str(texturesLength))
356 | # Simulate
357 | elif addonSettings.getSetting("ThumbnailSelectDeleteMove") == "2":
358 | log(str(thumbnailFilesLength) + " file(s) copied. " + humanReadableSizeOf(self.newThumbnailFileSize) + " recoverable")
359 | log(str(texturesLength) + " field(s) would be deleted from the textures database")
360 | if showGUI:
361 | xbmcgui.Dialog().ok(addonName + " - " + normalize(addonLanguage(32145)), normalize(addonLanguage(32149)) % (str(thumbnailFilesLength), humanReadableSizeOf(self.newThumbnailFileSize)), normalize(addonLanguage(32150)) % str(texturesLength))
362 |
363 | def SearchAndDeleteThumbnail(self):
364 | keyboard = xbmc.Keyboard()
365 | keyboard.doModal()
366 | query = keyboard.getText()
367 | if query:
368 | lists = []; query = query.replace("'", "''")
369 | match = RawXBMC.Execute("SELECT url FROM texture WHERE url LIKE '%" + query + "%'")
370 | for base in match: lists.append(base[0])
371 | if len(lists) > 0:
372 | selected = xbmcgui.Dialog().multiselect(normalize(addonLanguage(32151)), lists)
373 | for sel in selected:
374 | url = str(lists[sel])
375 | url = url.replace("'", "''")
376 | cachedUrl = RawXBMC.Execute("SELECT cachedurl FROM texture WHERE url='" + url + "'")
377 | cachedUrlPath = os.path.join(thumbnailsFolder, cachedUrl[0][0])
378 | try: shutil.move(cachedUrlPath, thumbnailBackupFolder)
379 | except: pass
380 | RawXBMC.Execute("DELETE FROM texture WHERE url='" + url + "'")
381 | if showGUI:
382 | self.ShowStats()
383 | elif (addonSettings.getSetting("ShowNotifications") == "true"):
384 | xbmc.executebuiltin("Notification(%s,%s,2000,%s)" % (addonName, normalize(addonLanguage(32152)), addonIcon))
385 |
386 | def EmptyThumbnailTable(self):
387 | self.Erase = xbmcgui.Dialog().yesno("%s" % addonName, "%s" % normalize(addonLanguage(32153)), "%s" % normalize(addonLanguage(32154)), " ", "%s" % "No", "%s" % "Yes")
388 | if self.Erase != 0:
389 | shutil.copy2(databaseFolder + "/Textures" + addonSettings.getSetting("TexturesDB") + ".db", databaseFolder + "/Textures" + addonSettings.getSetting("TexturesDB") + ".db.bak")
390 | RawXBMC.Execute("DELETE FROM texture")
391 | log("A copy of the textures database has been created")
392 | log("Textures database has been emptied")
393 | if showGUI:
394 | xbmcgui.Dialog().ok("%s - %s" % (addonName, normalize("Info")), "%s" % normalize(addonLanguage(32155)), normalize(addonLanguage(32156)))
395 | self.ShowStats()
396 |
397 | def GetFolderSize(self, start_path = '.'):
398 | total_size = 0
399 | for dirpath, dirnames, filenames in os.walk(start_path):
400 | for f in filenames:
401 | fp = os.path.join(dirpath, f)
402 | total_size += os.path.getsize(fp)
403 | return total_size
404 |
405 | def AddonCleanup(self):
406 | self.startedAt = datetime.datetime.now()
407 | if addonSettings.getSetting("ThumbnailSelectDeleteMove") == "2": log("Addon deletion simulation started at " + self.startedAt.strftime("%Y-%m-%d %H:%M:%d"))
408 | else: log("Addon cleanup started at " + self.startedAt.strftime("%Y-%m-%d %H:%M:%d"))
409 |
410 | jSonQuery = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "Addons.GetAddons", "params": {"properties": ["name","path"]}, "id": 1}')
411 | jSon = json.loads(jSonQuery)
412 | installedAddons = []
413 | if jSon['result']:
414 | for addon in jSon['result']['addons']:
415 | rest, realname = os.path.split(addon["path"])
416 | installedAddons.append(addon["addonid"])
417 | installedAddons.append(realname)
418 |
419 | self.totalAddonSize = 0
420 | self.deletedAddonSize = 0
421 | self.deletedAddonNumber = 0
422 | # Compute total size.
423 | addonData = os.path.join(userdataFolder, "addon_data")
424 | for item in os.listdir(addonData):
425 | self.totalAddonSize = self.totalAddonSize + self.GetFolderSize(os.path.join(addonData, item))
426 | addonRollback = os.path.join(homeFolder, "addons", "packages")
427 | for item in os.listdir(addonRollback):
428 | self.totalAddonSize = self.totalAddonSize + os.stat(os.path.join(addonRollback, item)).st_size
429 |
430 | if self.cancelOperation != True:
431 | if (addonSettings.getSetting("DelAddonsSettings") == "true"):
432 | log("Deleting addon settings for uninstalled addons")
433 | addonData = os.path.join(userdataFolder, "addon_data")
434 | toDelete = []
435 | for item in os.listdir(addonData):
436 | if (item not in installedAddons):
437 | toDelete.append(os.path.join(addonData, item))
438 | countList = 1
439 | for item in toDelete:
440 | self.deletedAddonSize = self.deletedAddonSize + self.GetFolderSize(item)
441 | self.deletedAddonNumber = self.deletedAddonNumber + 1
442 | rest, realname = os.path.split(item)
443 | # Delete
444 | if addonSettings.getSetting("AddonSelectDeleteMove") == "1":
445 | log("Deleting settings for " + realname + " from " + item)
446 | if showGUI:
447 | self.Progress.update((countList * 100) / len(toDelete), normalize(addonLanguage(32157)) + realname)
448 | try: shutil.rmtree(item)
449 | except: pass
450 | # Move
451 | elif addonSettings.getSetting("AddonSelectDeleteMove") == "0":
452 | log("Moving settings for " + realname + " from " + item)
453 | if showGUI:
454 | self.Progress.update((countList * 100) / len(toDelete), normalize(addonLanguage(32158)) + realname)
455 | try: shutil.rmtree(os.path.join(addonBackupFolder, realname))
456 | except: pass
457 | try: shutil.move(item, addonBackupFolder)
458 | except: pass
459 | # Simulate
460 | elif addonSettings.getSetting("AddonSelectDeleteMove") == "2":
461 | log("Copying settings for " + realname + " from " + item)
462 | if showGUI:
463 | self.Progress.update((countList * 100) / len(toDelete), normalize(addonLanguage(32159)) + realname)
464 | try: shutil.rmtree(os.path.join(addonBackupFolder, realname))
465 | except: pass
466 | try: shutil.copytree(item, os.path.join(addonBackupFolder, realname))
467 | except: pass
468 |
469 | if showGUI:
470 | if self.Progress.iscanceled():
471 | self.cancelOperation = True
472 | break
473 | countList = countList + 1
474 |
475 | if self.cancelOperation != True:
476 | if (addonSettings.getSetting("DelAddonsPackages") == "true"):
477 | log("Deleting addon packages for uninstalled addons")
478 | addonRollback = os.path.join(homeFolder, "addons", "packages")
479 | toDelete = []
480 | for item in os.listdir(addonRollback):
481 | splits = item.split('-')
482 | if (splits[0] not in installedAddons):
483 | toDelete.append(os.path.join(addonRollback, item))
484 | countList = 1
485 | for item in toDelete:
486 | self.deletedAddonSize = self.deletedAddonSize + os.stat(item).st_size
487 | self.deletedAddonNumber = self.deletedAddonNumber + 1
488 | rest, realname = os.path.split(item)
489 | # Delete
490 | if addonSettings.getSetting("AddonSelectDeleteMove") == "1":
491 | log("Deleting package " + item)
492 | if showGUI:
493 | self.Progress.update((countList * 100) / len(toDelete), normalize(addonLanguage(32160)) + item)
494 | try: os.remove(item)
495 | except: pass
496 | # Move
497 | elif addonSettings.getSetting("AddonSelectDeleteMove") == "0":
498 | log("Moving package " + item)
499 | if showGUI:
500 | self.Progress.update((countList * 100) / len(toDelete), normalize(addonLanguage(32161)) + item)
501 | try: os.remove(os.path.join(addonBackupFolder, realname))
502 | except: pass
503 | try: shutil.move(item, addonBackupFolder)
504 | except: pass
505 | # Simulate
506 | elif addonSettings.getSetting("AddonSelectDeleteMove") == "2":
507 | log("Copying package " + item)
508 | if showGUI:
509 | self.Progress.update((countList * 100) / len(toDelete), normalize(addonLanguage(32162)) + item)
510 | try: shutil.copy2(item, addonBackupFolder)
511 | except: pass
512 |
513 | if showGUI:
514 | if self.Progress.iscanceled():
515 | self.cancelOperation = True
516 | break
517 | countList = countList + 1
518 |
519 | if self.cancelOperation != True:
520 | if (addonSettings.getSetting("LimitAddonsPackages") == "true"):
521 | log("Trimming history of addon packages")
522 | historyKeep = int(addonSettings.getSetting("NumAddonsPackages"))
523 | addonRollback = os.path.join(homeFolder, "addons", "packages")
524 | files = os.listdir(addonRollback)
525 | # Sort list of files to make sure versions are ordered correctly
526 | convert = lambda text: int(text) if text.isdigit() else text
527 | alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
528 | files.sort(key=alphanum_key)
529 | dict = {}
530 | for item in files:
531 | splits = item.split('-')
532 | if dict.get(splits[0]) == None:
533 | dict[splits[0]] = []
534 | dict[splits[0]].append(item)
535 | toDelete = []
536 | for key in dict:
537 | delNum = len(dict.get(key)) - historyKeep
538 | while delNum > 0:
539 | toDelete.append(os.path.join(addonRollback, dict.get(key)[len(dict.get(key)) - (delNum + historyKeep)]))
540 | delNum = delNum - 1
541 | countList = 1
542 | for item in toDelete:
543 | self.deletedAddonSize = self.deletedAddonSize + os.stat(item).st_size
544 | self.deletedAddonNumber = self.deletedAddonNumber + 1
545 | rest, realname = os.path.split(item)
546 | # Delete
547 | if addonSettings.getSetting("AddonSelectDeleteMove") == "1":
548 | log("Deleting package " + item)
549 | if showGUI:
550 | self.Progress.update((countList * 100) / len(toDelete), normalize(addonLanguage(32160)) + item)
551 | try: os.remove(item)
552 | except: pass
553 | # Move
554 | elif addonSettings.getSetting("AddonSelectDeleteMove") == "0":
555 | log("Moving package " + item)
556 | if showGUI:
557 | self.Progress.update((countList * 100) / len(toDelete), normalize(addonLanguage(32161)) + item)
558 | try: os.remove(os.path.join(addonBackupFolder, realname))
559 | except: pass
560 | try: shutil.move(item, addonBackupFolder)
561 | except: pass
562 | # Simulate
563 | elif addonSettings.getSetting("AddonSelectDeleteMove") == "2":
564 | log("Copying package " + item)
565 | if showGUI:
566 | self.Progress.update((countList * 100) / len(toDelete), normalize(addonLanguage(32162)) + item)
567 | try: shutil.copy2(item, addonBackupFolder)
568 | except: pass
569 |
570 | if showGUI:
571 | if self.Progress.iscanceled():
572 | self.cancelOperation = True
573 | break
574 | countList = countList + 1
575 |
576 | self.finishAt = datetime.datetime.now()
577 | self.tookTime = self.finishAt - self.startedAt
578 | log("Addon cleanup terminated in " + str(self.tookTime).split(".")[0])
579 | log("Total addon size = " + str(humanReadableSizeOf(self.totalAddonSize)))
580 |
581 | # Manage Textual Response
582 | # Delete
583 | if addonSettings.getSetting("ThumbnailSelectDeleteMove") == "1":
584 | log(str(self.deletedAddonNumber) + " file(s) deleted. " + humanReadableSizeOf(self.deletedAddonSize) + " recovered")
585 | if showGUI:
586 | xbmcgui.Dialog().ok(addonName + " - " + normalize(addonLanguage(32163)), normalize(addonLanguage(32146)) % (str(self.deletedAddonNumber), humanReadableSizeOf(self.deletedAddonSize)))
587 | # Move
588 | elif addonSettings.getSetting("ThumbnailSelectDeleteMove") == "0":
589 | log(str(self.deletedAddonNumber) + " file(s) moved. " + humanReadableSizeOf(self.deletedAddonSize) + " recovered")
590 | if showGUI:
591 | xbmcgui.Dialog().ok(addonName + " - " + normalize(addonLanguage(32163)), normalize(addonLanguage(32148)) % (str(self.deletedAddonNumber), humanReadableSizeOf(self.deletedAddonSize)))
592 | # Simulate
593 | elif addonSettings.getSetting("ThumbnailSelectDeleteMove") == "2":
594 | log(str(self.deletedAddonNumber) + " file(s) copied. " + humanReadableSizeOf(self.deletedAddonSize) + " recoverable")
595 | if showGUI:
596 | xbmcgui.Dialog().ok(addonName + " - " + normalize(addonLanguage(32163)), normalize(addonLanguage(32149)) % (str(self.deletedAddonNumber), humanReadableSizeOf(self.deletedAddonSize)))
597 |
598 | def PerformCleanup(self, bitmask):
599 | if addonSettings.getSetting("ShowNotifications") == "true" and not showGUI:
600 | xbmc.executebuiltin("Notification(%s,%s,2000,%s)" % (addonName, normalize(addonLanguage(32164)), addonIcon))
601 |
602 | if (bitmask > 0 and showGUI):
603 | self.Progress = xbmcgui.DialogProgress()
604 | self.Progress.create(addonName)
605 |
606 | if ((bitmask & 1) == 1):
607 | self.ThumbnailCleanup()
608 | if ((bitmask & 2) == 2):
609 | self.SearchAndDeleteThumbnail()
610 | if ((bitmask & 4) == 4):
611 | self.EmptyThumbnailTable()
612 | if ((bitmask & 8) == 8):
613 | self.AddonCleanup()
614 |
615 | if (bitmask > 0 and showGUI):
616 | self.Progress.close()
617 |
618 | if addonSettings.getSetting("ShowNotifications") == "true" and not showGUI:
619 | xbmc.executebuiltin("Notification(%s,%s,2000,%s)" % (addonName, normalize(addonLanguage(32165)), addonIcon))
620 |
621 | if (__name__ == "__main__"):
622 | # If show main manu option is enabled, show main menu in a loop
623 | if addonSettings.getSetting("ShowMainMenu") == "true":
624 | remain = True
625 | while remain == True:
626 | remain = False
627 | selection = xbmcgui.Dialog().select(addonName, [normalize(addonLanguage(32100)),
628 | normalize(addonLanguage(32101)), normalize(addonLanguage(32102)),
629 | normalize(addonLanguage(32103)), normalize(addonLanguage(32104)),
630 | normalize(addonLanguage(32105))])
631 | if selection == 0: remain = True; Cleaner().PerformCleanup(1)
632 | elif selection == 1: remain = True; Cleaner().PerformCleanup(2)
633 | elif selection == 2: remain = True; Cleaner().PerformCleanup(4)
634 | elif selection == 3: remain = True; Cleaner().PerformCleanup(8)
635 | elif selection == 4: remain = True; Cleaner().ShowStats(True)
636 | elif selection == 5: remain = True; addonSettings.openSettings()
637 | # Otherwise perform both main cleanup actions in sequence
638 | else:
639 | Cleaner().PerformCleanup(9)
640 | del addonSettings
--------------------------------------------------------------------------------