################################################################################ # Copyright (C) 2019 drinfernoo # # # # 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, 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 XBMC; see the file COPYING. If not, write to # # the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. # # http://www.gnu.org/copyleft/gpl.html # ################################################################################ import xbmc import xbmcaddon import xbmcgui import xbmcvfs import sys import glob import shutil import re import os try: # Python 3 from urllib.parse import quote_plus import zipfile except ImportError: # Python 2 from urllib import quote_plus from resources.libs import zipfile from resources.libs.common.config import CONFIG from resources.libs import db from resources.libs.common import logging from resources.libs.common import tools def cleanup_backup(): folder = glob.glob(os.path.join(CONFIG.MYBUILDS, "*")) logging.log(folder) list = [] filelist = [] dialog = xbmcgui.Dialog() if len(folder) == 0: logging.log_notify(CONFIG.ADDONTITLE, "[COLOR {0}]Backup Location: Empty[/COLOR]".format(CONFIG.COLOR2)) return for item in sorted(folder, key=os.path.getmtime): filelist.append(item) base = item.replace(CONFIG.MYBUILDS, '') if os.path.isdir(item): list.append('/{0}/'.format(base)) elif os.path.isfile(item): list.append(base) list = ['--- Remove All Items ---'] + list selected = dialog.select("{0}: Select the items to remove from the 'My_Builds' folder.".format(CONFIG.ADDONTITLE), list) if selected == -1: logging.log_notify(CONFIG.ADDONTITLE, "[COLOR {0}]Clean Up Cancelled![/COLOR]".format(CONFIG.COLOR2)) elif selected == 0: if dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]Would you like to clean up all items in your 'My_Builds' folder?[/COLOR]".format( CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, CONFIG.MYBUILDS), yeslabel="[B][COLOR springgreen]Clean Up[/COLOR][/B]", nolabel="[B][COLOR red]No Cancel[/COLOR][/B]"): clearedfiles, clearedfolders = tools.clean_house(CONFIG.MYBUILDS) logging.log_notify(CONFIG.ADDONTITLE, "[COLOR {0}]Removed Files: [COLOR {1}]{2}[/COLOR] / Folders:[/COLOR] [COLOR {3}]{4}[/COLOR]".format( CONFIG.COLOR2, CONFIG.COLOR1, clearedfiles, CONFIG.COLOR1, clearedfolders)) else: logging.log_notify(CONFIG.ADDONTITLE, "[COLOR {0}]Clean Up Cancelled![/COLOR]".format(CONFIG.COLOR2)) else: path = filelist[selected - 1] passed = False if dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]Would you like to remove [COLOR {1}]{2}[/COLOR] from the 'My_Builds' folder?[/COLOR]".format( CONFIG.COLOR2, CONFIG.COLOR1, list[selected]), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, path), yeslabel="[B][COLOR springgreen]Clean Up[/COLOR][/B]", nolabel="[B][COLOR red]No Cancel[/COLOR][/B]"): if os.path.isfile(path): try: os.remove(path) passed = True except: logging.log("Unable to remove: {0}".format(path)) else: tools.clean_house(path) try: shutil.rmtree(path) passed = True except Exception as e: logging.log("Error removing {0}: {1}".format(path, e)) if passed: logging.log_notify(CONFIG.ADDONTITLE, "[COLOR {0}]{1} Removed![/COLOR]".format(CONFIG.COLOR2, list[selected])) else: logging.log_notify(CONFIG.ADDONTITLE, "[COLOR {0}]Error Removing {1}![/COLOR]".format(CONFIG.COLOR2, list[selected])) else: logging.log_notify(CONFIG.ADDONTITLE, "[COLOR {0}]Clean Up Cancelled![/COLOR]".format(CONFIG.COLOR2)) class Backup: def __init__(self): tools.ensure_folders() self.dialog = xbmcgui.Dialog() self.progress_dialog = xbmcgui.DialogProgress() def backup_addon_pack(self, name=""): if self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]Are you sure you wish to create an Addon Pack?[/COLOR]".format(CONFIG.COLOR2), nolabel="[B][COLOR red]Cancel Backup[/COLOR][/B]", yeslabel="[B][COLOR springgreen]Create Pack[/COLOR][/B]"): if name == "": name = tools.get_keyboard("", "Please enter a name for the add-on pack zip") if not name: return False name = quote_plus(name) name = '{0}.zip'.format(name) tempzipname = '' zipname = os.path.join(CONFIG.MYBUILDS, name) try: zipf = zipfile.ZipFile(xbmc.translatePath(zipname), mode='w', allowZip64=True) except: try: tempzipname = os.path.join(CONFIG.PACKAGES, '{0}.zip'.format(name)) zipf = zipfile.ZipFile(tempzipname, mode='w', allowZip64=True) except: logging.log("Unable to create {0}.zip".format(name), level=xbmc.LOGERROR) if self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]We are unable to write to the current backup directory, would you like to change the location?[/COLOR]".format( CONFIG.COLOR2), yeslabel="[B][COLOR springgreen]Change Directory[/COLOR][/B]", nolabel="[B][COLOR red]Cancel[/COLOR][/B]"): CONFIG.open_settings() return else: return fold = glob.glob(os.path.join(CONFIG.ADDONS, '*/')) addonnames = [] addonfolds = [] for folder in sorted(fold, key=lambda x: x): foldername = os.path.split(folder[:-1])[1] if foldername in CONFIG.EXCLUDES: continue elif foldername in CONFIG.DEFAULTPLUGINS: continue elif foldername == 'packages': continue xml = os.path.join(folder, 'addon.xml') if os.path.exists(xml): match = tools.parse_dom(tools.read_from_file(xml), 'addon', ret='name') if len(match) > 0: addonnames.append(match[0]) addonfolds.append(foldername) else: addonnames.append(foldername) addonfolds.append(foldername) selected = self.dialog.multiselect( "{0}: Select the add-ons you wish to add to the zip.".format(CONFIG.ADDONTITLE), addonnames) if selected is None: selected = [] logging.log(selected) self.progress_dialog.create(CONFIG.ADDONTITLE, '[COLOR {0}][B]Creating Zip File:[/B][/COLOR]'.format(CONFIG.COLOR2), '', 'Please Wait') if len(selected) > 0: added = [] for item in selected: added.append(addonfolds[item]) self.progress_dialog.update(0, "", "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, addonfolds[item])) for base, dirs, files in os.walk(os.path.join(CONFIG.ADDONS, addonfolds[item])): files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: if file.endswith('.pyo'): continue self.progress_dialog.update(0, "", "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, addonfolds[item]), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, file)) fn = os.path.join(base, file) zipf.write(fn, fn[len(CONFIG.ADDONS):], zipfile.ZIP_DEFLATED) dep = os.path.join(CONFIG.ADDONS, addonfolds[item], 'addon.xml') if os.path.exists(dep): match = tools.parse_dom(tools.read_from_file(dep), 'import', ret='addon') for depends in match: if 'xbmc.python' in depends: continue if depends in added: continue self.progress_dialog.update(0, "", "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, depends)) for base, dirs, files in os.walk(os.path.join(CONFIG.ADDONS, depends)): files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: if file.endswith('.pyo'): continue self.progress_dialog.update(0, "", "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, depends), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, file)) fn = os.path.join(base, file) zipf.write(fn, fn[len(CONFIG.ADDONS):], zipfile.ZIP_DEFLATED) added.append(depends) self.dialog.ok(CONFIG.ADDONTITLE, "[COLOR {0}]{1}[/COLOR] [COLOR {2}]Backup successful:[/COLOR]".format(CONFIG.COLOR1, name, CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, zipname)) def backup_build(self, name=""): if self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]Are you sure you wish to backup the current build?[/COLOR]".format( CONFIG.COLOR2), nolabel="[B][COLOR red]Cancel Backup[/COLOR][/B]", yeslabel="[B][COLOR springgreen]Backup Build[/COLOR][/B]"): if name == "": name = tools.get_keyboard("", "Please enter a name for the build zip") if not name: return False name = name.replace('\\', '').replace('/', '').replace(':', '').replace('*', '').replace('?', '').replace( '"', '').replace('<', '').replace('>', '').replace('|', '') name = quote_plus(name) tempzipname = '' zipname = os.path.join(CONFIG.MYBUILDS, '{0}.zip'.format(name)) for_progress = 0 ITEM = [] exclude_dirs = CONFIG.EXCLUDE_DIRS if not self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]Do you want to include your addon_data folder?".format(CONFIG.COLOR2), "This contains [COLOR {0}]ALL[/COLOR] add-on settings including passwords but may also contain important information such as skin shortcuts. We recommend [COLOR {0}]MANUALLY[/COLOR] removing the addon_data folders that aren\'t required.".format( CONFIG.COLOR1, CONFIG.COLOR1), "[COLOR {0}]{1}[/COLOR] addon_data is ignored[/COLOR]".format(CONFIG.COLOR1, CONFIG.ADDON_ID), yeslabel='[B][COLOR springgreen]Include data[/COLOR][/B]', nolabel='[B][COLOR red]Don\'t Include[/COLOR][/B]'): exclude_dirs.append('addon_data') tools.convert_special(CONFIG.HOME, True) extractsize = 0 try: zipf = zipfile.ZipFile(xbmc.translatePath(zipname), mode='w', allowZip64=True) except: try: tempzipname = os.path.join(CONFIG.PACKAGES, '{0}.zip'.format(name)) zipf = zipfile.ZipFile(tempzipname, mode='w', allowZip64=True) except: logging.log("Unable to create {0}.zip".format(name), level=xbmc.LOGERROR) if self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]We are unable to write to the current backup directory, would you like to change the location?[/COLOR]".format( CONFIG.COLOR2), yeslabel="[B][COLOR springgreen]Change Directory[/COLOR][/B]", nolabel="[B][COLOR red]Cancel[/COLOR][/B]"): CONFIG.open_settings() return else: return self.progress_dialog.create(CONFIG.ADDONTITLE + "[COLOR {0}]: Creating Zip[/COLOR]".format(CONFIG.COLOR2), "[COLOR {0}]Creating backup zip".format(CONFIG.COLOR2), "", "Please Wait...[/COLOR]") for base, dirs, files in os.walk(CONFIG.HOME): dirs[:] = [d for d in dirs if d not in exclude_dirs] files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: ITEM.append(file) N_ITEM = len(ITEM) picture = [] music = [] video = [] programs = [] repos = [] scripts = [] skins = [] fold = glob.glob(os.path.join(CONFIG.ADDONS, '*/')) idlist = [] binaries = [] binidlist = [] for folder in sorted(fold, key=lambda x: x): foldername = os.path.split(folder[:-1])[1] if foldername == 'packages': continue binaryid, binaryname = db.find_binary_addons(addon=foldername) if binaryid: binaries.append(binaryname) binidlist.append(binaryid) xml = os.path.join(folder, 'addon.xml') if os.path.exists(xml): a = tools.read_from_file(xml) prov = re.compile("<provides>(.+?)</provides>").findall(a) match = tools.parse_dom(prov, 'addon', ret='id') addid = foldername if len(match) == 0 else match[0] if addid in idlist: continue idlist.append(addid) try: add = xbmcaddon.Addon(id=addid) aname = add.getAddonInfo('name') aname = aname.replace('[', '<').replace(']', '>') aname = str(re.sub('<[^<]+?>', '', aname)).lstrip() except: aname = foldername if len(prov) == 0: if foldername.startswith('skin'): skins.append(aname) elif foldername.startswith('repo'): repos.append(aname) else: scripts.append(aname) continue if not (prov[0]).find('executable') == -1: programs.append(aname) if not (prov[0]).find('video') == -1: video.append(aname) if not (prov[0]).find('audio') == -1: music.append(aname) if not (prov[0]).find('image') == -1: picture.append(aname) db.fix_metas() binarytxt = self._backup_binaries(binidlist) for base, dirs, files in os.walk(CONFIG.HOME): dirs[:] = [d for d in dirs if d not in exclude_dirs] files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: try: for_progress += 1 progress = tools.percentage(for_progress, N_ITEM) self.progress_dialog.update(int(progress), '[COLOR {0}]Creating backup zip: [COLOR {1}]{2}[/COLOR] / [COLOR {3}]{4}[/COLOR]'.format( CONFIG.COLOR2, CONFIG.COLOR1, for_progress, CONFIG.COLOR1, N_ITEM), '[COLOR {0}]{1}[/COLOR]'.format(CONFIG.COLOR1, file), '') fn = os.path.join(base, file) if file in CONFIG.LOGFILES: logging.log("[Back Up] Type = build: Ignore {0} - Log File".format(file)) continue elif os.path.join(base, file) in CONFIG.EXCLUDE_FILES: logging.log("[Back Up] Type = build: Ignore {0} - Excluded File".format(file)) continue elif os.path.join('addons', 'packages') in fn: logging.log("[Back Up] Type = build: Ignore {0} - Packages Folder".format(file)) continue elif file.startswith('._') or file.lower().startswith('.ds_store'): logging.log("[Back Up] Type = build: Ignore {0} - OSX metadata file".format(file)) continue elif file.endswith('.pyo'): continue elif file.lower().endswith('.db') and 'database' in base: temp = file.replace('.db', '') temp = ''.join([i for i in temp if not i.isdigit()]) if temp in CONFIG.DB_FILES: if not file == db.latest_db(temp): logging.log("[Back Up] Type = build: Ignore {0} - DB File".format(file)) continue skipbinary = False if len(binidlist) > 0: for id in binidlist: id = os.path.join(CONFIG.ADDONS, id) if id in fn: skipbinary = True if skipbinary: logging.log("[Back Up] Type = build: Ignore {0} - Binary Add-on".format(file)) continue try: zipf.write(fn, fn[len(CONFIG.HOME):], zipfile.ZIP_DEFLATED) extractsize += os.path.getsize(fn) except Exception as e: logging.log("[Back Up] Type = build: Unable to backup {0}".format(file)) logging.log("{0} / {1}".format(Exception, e)) if self.progress_dialog.iscanceled(): self.progress_dialog.close() logging.log_notify(CONFIG.ADDONTITLE, "[COLOR {0}]Backup Cancelled[/COLOR]".format(CONFIG.COLOR2)) sys.exit() except Exception as e: logging.log("[Back Up] Type = build: Unable to backup {0}".format(file)) logging.log("Build Backup Error: {0}".format(str(e))) if 'addon_data' in exclude_dirs: match = glob.glob(os.path.join(CONFIG.ADDON_DATA, 'skin.*', '')) for fold in match: fd = os.path.split(fold[:-1])[1] if fd not in ['skin.confluence', 'skin.estuary', 'skin.estouchy']: for base, dirs, files in os.walk(os.path.join(CONFIG.ADDON_DATA, fold)): files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: fn = os.path.join(base, file) zipf.write(fn, fn[len(CONFIG.HOME):], zipfile.ZIP_DEFLATED) extractsize += os.path.getsize(fn) xml = os.path.join(CONFIG.ADDONS, fd, 'addon.xml') if os.path.exists(xml): matchxml = tools.parse_dom(tools.read_from_file(xml), 'import', ret='addon') if 'script.skinshortcuts' in matchxml: for base, dirs, files in os.walk( os.path.join(CONFIG.ADDON_DATA, 'script.skinshortcuts')): files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: fn = os.path.join(base, file) zipf.write(fn, fn[len(CONFIG.HOME):], zipfile.ZIP_DEFLATED) extractsize += os.path.getsize(fn) zipf.close() xbmc.sleep(500) self.progress_dialog.close() backup('gui', name) if not tempzipname == '': success = xbmcvfs.rename(tempzipname, zipname) if success == 0: xbmcvfs.copy(tempzipname, zipname) xbmcvfs.delete(tempzipname) if binarytxt is not None: bintxtpath = os.path.join(CONFIG.USERDATA, binarytxt) xbmcvfs.delete(bintxtpath) self._backup_info(name, extractsize, programs, video, music, picture, repos, scripts, binaries) if len(binaries) > 0: self.dialog.ok(CONFIG.ADDONTITLE, "[COLOR {0}]The following add-ons were excluded from the backup because they are platform specific:[/COLOR]".format( CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, ', '.join(binaries))) self.dialog.ok(CONFIG.ADDONTITLE, "[COLOR {0}]{1}[/COLOR] [COLOR {2}]Backup successful:[/COLOR]".format(CONFIG.COLOR1, name, CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, zipname)) def _backup_info(self, name, extractsize, programs, video, music, picture, repos, scripts, binaries): backup_path = CONFIG.MYBUILDS zipname = name + '.zip' txtname = name + '.txt' backup_zip = os.path.join(backup_path, zipname) temp_txt = os.path.join(CONFIG.PACKAGES, txtname) info_txt = os.path.join(backup_path, txtname) _skin_root = xbmc.translatePath('special://skin/') _skin_id = os.path.basename(os.path.normpath(_skin_root)) _skin = xbmcaddon.Addon(_skin_id) _skin_name = xbmc.translatePath(_skin.getAddonInfo('name')) with open(temp_txt, 'w') as f: f.write('name="{0}"\n'.format(name)) f.write('extracted="{0}"\n'.format(extractsize)) f.write('zipsize="{0}"\n'.format(os.path.getsize(backup_zip))) f.write('skin="{0}"\n'.format(_skin_name)) f.write('created="{0}"\n'.format(tools.get_date(formatted=True))) f.write('programs="{0}"\n'.format(', '.join(programs)) if len(programs) > 0 else 'programs="none"\n') f.write('video="{0}"\n'.format(', '.join(video)) if len(video) > 0 else 'video="none"\n') f.write('music="{0}"\n'.format(', '.join(music)) if len(music) > 0 else 'music="none"\n') f.write('picture="{0}"\n'.format(', '.join(picture)) if len(picture) > 0 else 'picture="none"\n') f.write('repos="{0}"\n'.format(', '.join(repos)) if len(repos) > 0 else 'repos="none"\n') f.write('scripts="{0}"\n'.format(', '.join(scripts)) if len(scripts) > 0 else 'scripts="none"\n') f.write('binaries="{0}"\n'.format(', '.join(binaries)) if len(binaries) > 0 else 'binaries="none"\n') xbmcvfs.copy(temp_txt, info_txt) xbmcvfs.delete(temp_txt) def _backup_binaries(self, ids): txtname = None if len(ids) > 0: path = CONFIG.USERDATA txtname = 'build_binaries.txt' txt_path = os.path.join(path, txtname) with open(txt_path, 'w') as f: for id in ids: if ids.index(id) == len(ids) - 1: f.write(id) else: f.write(id + ',') return txtname def backup_gui(self, name=""): if name == "": guiname = tools.get_keyboard("", "Please enter a name for the GUI Fix zip") if not guiname: return False tools.convert_special(CONFIG.USERDATA, True) tools.ascii_check(CONFIG.USERDATA, True) else: guiname = name guiname = quote_plus(guiname) tempguizipname = '' guizipname = os.path.join(CONFIG.MYBUILDS, '{0}_guisettings.zip'.format(guiname)) if os.path.exists(CONFIG.GUISETTINGS): try: zipf = zipfile.ZipFile(guizipname, mode='w', allowZip64=True) except: try: tempguizipname = os.path.join(CONFIG.PACKAGES, '{0}_guisettings.zip'.format(guiname)) zipf = zipfile.ZipFile(tempguizipname, mode='w', allowZip64=True) except: logging.log("Unable to create {0}_guisettings.zip".format(guiname), level=xbmc.LOGERROR) if self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]We are unable to write to the current backup directory, would you like to change the location?[/COLOR]".format( CONFIG.COLOR2), yeslabel="[B][COLOR springgreen]Change Directory[/COLOR][/B]", nolabel="[B][COLOR red]Cancel[/COLOR][/B]"): CONFIG.open_settings() return else: return try: zipf.write(CONFIG.GUISETTINGS, 'guisettings.xml', zipfile.ZIP_DEFLATED) zipf.write(CONFIG.PROFILES, 'profiles.xml', zipfile.ZIP_DEFLATED) match = glob.glob(os.path.join(CONFIG.ADDON_DATA, 'skin.*', '')) for fold in match: fd = os.path.split(fold[:-1])[1] if fd not in ['skin.confluence', 'skin.estuary', 'skin.estouchy']: if self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]Would you like to add the following skin folder to the GUI Fix Zip File?[/COLOR]".format( CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, fd), yeslabel="[B][COLOR springgreen]Add Skin[/COLOR][/B]", nolabel="[B][COLOR red]Skip Skin[/COLOR][/B]"): for base, dirs, files in os.walk(os.path.join(CONFIG.ADDON_DATA, fold)): files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: fn = os.path.join(base, file) zipf.write(fn, fn[len(CONFIG.USERDATA):], zipfile.ZIP_DEFLATED) xml = os.path.join(CONFIG.ADDONS, fd, 'addon.xml') if os.path.exists(xml): matchxml = tools.parse_dom(tools.read_from_file(xml), 'import', ret='addon') if 'script.skinshortcuts' in matchxml: for base, dirs, files in os.walk( os.path.join(CONFIG.ADDON_DATA, 'script.skinshortcuts')): files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: fn = os.path.join(base, file) zipf.write(fn, fn[len(CONFIG.USERDATA):], zipfile.ZIP_DEFLATED) else: logging.log("[Back Up] Type = guifix: {0} ignored".format(fold)) except Exception as e: logging.log("[Back Up] Type = guifix: {0}".format(e)) pass zipf.close() if not tempguizipname == '': success = xbmcvfs.rename(tempguizipname, guizipname) if success == 0: xbmcvfs.copy(tempguizipname, guizipname) xbmcvfs.delete(tempguizipname) else: logging.log("[Back Up] Type = guifix: guisettings.xml not found") if name == "": self.dialog.ok(CONFIG.ADDONTITLE, "[COLOR {0}]GUI Fix backup successful:[/COLOR]".format(CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, guizipname)) def backup_theme(self, name=""): if not self.dialog.yesno(CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to create a theme backup?[/COLOR]".format(CONFIG.COLOR2), yeslabel="[B][COLOR springgreen]Continue[/COLOR][/B]", nolabel="[B][COLOR red]No Cancel[/COLOR][/B]"): logging.log_notify("Theme Backup", "Cancelled!") return False if name == "": themename = tools.get_keyboard("", "Please enter a name for the theme zip") if not themename: return False else: themename = name themename = quote_plus(themename) tempzipname = '' zipname = os.path.join(CONFIG.MYBUILDS, '{0}.zip'.format(themename)) try: zipf = zipfile.ZipFile(zipname, mode='w', allowZip64=True) except: try: tempzipname = os.path.join(CONFIG.PACKAGES, '{0}.zip'.format(themename)) zipf = zipfile.ZipFile(tempzipname, mode='w', allowZip64=True) except: logging.log("Unable to create {0}.zip".format(themename), level=xbmc.LOGERROR) if self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]We are unable to write to the current backup directory, would you like to change the location?[/COLOR]".format( CONFIG.COLOR2), yeslabel="[B][COLOR springgreen]Change Directory[/COLOR][/B]", nolabel="[B][COLOR red]Cancel[/COLOR][/B]"): CONFIG.open_settings() return else: return tools.convert_special(CONFIG.USERDATA, True) tools.ascii_check(CONFIG.USERDATA, True) try: if not CONFIG.SKIN not in ['skin.confluence', 'skin.estuary', 'skin.estouchy']: skinfold = os.path.join(CONFIG.SKIN, 'media') match2 = glob.glob(os.path.join(skinfold, '*.xbt')) if len(match2) > 1: if self.dialog.yesno(CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to go through the Texture Files for?[/COLOR]".format( CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, CONFIG.SKIN), yeslabel="[B][COLOR springgreen]Add Textures[/COLOR][/B]", nolabel="[B][COLOR red]Skip Textures[/COLOR][/B]"): for xbt in match2: if self.dialog.yesno( CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to add the Texture File [COLOR {1}]{2}[/COLOR]?".format( CONFIG.COLOR1, CONFIG.COLOR2, xbt.replace(skinfold, "")[1:]), "from [COLOR {0}]{1}[/COLOR][/COLOR]".format(CONFIG.COLOR1, CONFIG.SKIN), yeslabel="[B][COLOR springgreen]Add Textures[/COLOR][/B]", nolabel="[B][COLOR red]Skip Textures[/COLOR][/B]"): fn = xbt fn2 = fn.replace(CONFIG.HOME, "") zipf.write(fn, fn2, zipfile.ZIP_DEFLATED) else: for xbt in match2: if self.dialog.yesno( CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to add the Texture File [COLOR {1}]{2}[/COLOR]?".format( CONFIG.COLOR2, CONFIG.COLOR1, xbt.replace(skinfold, "")[1:]), "from [COLOR {0}]{1}[/COLOR][/COLOR]".format(CONFIG.COLOR1, CONFIG.SKIN), yeslabel="[B][COLOR springgreen]Add Textures[/COLOR][/B]", nolabel="[B][COLOR red]Skip Textures[/COLOR][/B]"): fn = xbt fn2 = fn.replace(CONFIG.HOME, "") zipf.write(fn, fn2, zipfile.ZIP_DEFLATED) ad_skin = os.path.join(CONFIG.ADDON_DATA, CONFIG.SKIN, 'settings.xml') if os.path.exists(ad_skin): if self.dialog.yesno(CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to go add the [COLOR {1}]settings.xml[/COLOR] in [COLOR {2}]/addon_data/[/COLOR] for?".format( CONFIG.COLOR2, CONFIG.COLOR1, CONFIG.COLOR1), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, CONFIG.SKIN), yeslabel="[B][COLOR springgreen]Add Settings[/COLOR][/B]", nolabel="[B][COLOR red]Skip Settings[/COLOR][/B]"): ad_skin2 = ad_skin.replace(CONFIG.HOME, "") zipf.write(ad_skin, ad_skin2, zipfile.ZIP_DEFLATED) match = tools.parse_dom(tools.read_from_file(os.path.join(CONFIG.SKIN, 'addon.xml')), 'import', ret='addon') if 'script.skinshortcuts' in match: if self.dialog.yesno(CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to go add the [COLOR {1}]settings.xml[/COLOR] for [COLOR {2}]script.skinshortcuts[/COLOR]?".format( CONFIG.COLOR2, CONFIG.COLOR1, CONFIG.COLOR1), yeslabel="[B][COLOR springgreen]Add Settings[/COLOR][/B]", nolabel="[B][COLOR red]Skip Settings[/COLOR][/B]"): for base, dirs, files in os.walk(os.path.join(CONFIG.ADDON_DATA, 'script.skinshortcuts')): files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: fn = os.path.join(base, file) zipf.write(fn, fn[len(CONFIG.HOME):], zipfile.ZIP_DEFLATED) if self.dialog.yesno(CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to include a [COLOR {1}]Backgrounds[/COLOR] folder?[/COLOR]".format( CONFIG.COLOR2, CONFIG.COLOR1), yeslabel="[B][COLOR springgreen]Yes Include[/COLOR][/B]", nolabel="[B][COLOR red]No Continue[/COLOR][/B]"): fn = self.dialog.browse(0, 'Select location of backgrounds', 'files', '', True, False, CONFIG.HOME, False) if not fn == CONFIG.HOME: for base, dirs, files in os.walk(fn): dirs[:] = [d for d in dirs if d not in CONFIG.EXCLUDE_DIRS] files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: try: fn2 = os.path.join(base, file) zipf.write(fn2, fn2[len(CONFIG.HOME):], zipfile.ZIP_DEFLATED) except Exception as e: logging.log("[Back Up] Type = theme: Unable to backup {0}".format(file)) logging.log("Backup Error: {0}".format(str(e))) text = db.latest_db('Textures') if self.dialog.yesno(CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to include the [COLOR {1}]{2}[/COLOR]?[/COLOR]".format( CONFIG.COLOR2, CONFIG.COLOR1, text), yeslabel="[B][COLOR springgreen]Yes Include[/COLOR][/B]", nolabel="[B][COLOR red]No Continue[/COLOR][/B]"): zipf.write(os.path.join(CONFIG.DATABASE, text), '/userdata/Database/{0}'.format(text), zipfile.ZIP_DEFLATED) if self.dialog.yesno(CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to include any addons?[/COLOR]".format(CONFIG.COLOR2), yeslabel="[B][COLOR springgreen]Yes Include[/COLOR][/B]", nolabel="[B][COLOR red]No Continue[/COLOR][/B]"): fold = glob.glob(os.path.join(CONFIG.ADDONS, '*/')) addonnames = [] addonfolds = [] for folder in sorted(fold, key=lambda x: x): foldername = os.path.split(folder[:-1])[1] if foldername in CONFIG.EXCLUDES: continue elif foldername in CONFIG.DEFAULTPLUGINS: continue elif foldername == 'packages': continue xml = os.path.join(folder, 'addon.xml') if os.path.exists(xml): match = tools.parse_dom(tools.read_from_file(xml), 'addon', ret='name') if len(match) > 0: addonnames.append(match[0]) addonfolds.append(foldername) else: addonnames.append(foldername) addonfolds.append(foldername) selected = self.dialog.multiselect( "{0}: Select the add-ons you wish to add to the zip.".format(CONFIG.ADDONTITLE), addonnames) if selected is None: selected = [] if len(selected) > 0: added = [] for item in selected: added.append(addonfolds[item]) for base, dirs, files in os.walk(os.path.join(CONFIG.ADDONS, addonfolds[item])): files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: if file.endswith('.pyo'): continue fn = os.path.join(base, file) zipf.write(fn, fn[len(CONFIG.HOME):], zipfile.ZIP_DEFLATED) dep = os.path.join(CONFIG.ADDONS, addonfolds[item], 'addon.xml') if os.path.exists(dep): match = tools.parse_dom(tools.read_from_file(dep), 'import', ret='addon') for depends in match: if 'xbmc.python' in depends: continue if depends in added: continue for base, dirs, files in os.walk(os.path.join(CONFIG.ADDONS, depends)): files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: if file.endswith('.pyo'): continue fn = os.path.join(base, file) zipf.write(fn, fn[len(CONFIG.HOME):], zipfile.ZIP_DEFLATED) added.append(depends) if self.dialog.yesno(CONFIG.ADDONTITLE + '[COLOR {0}]: Theme Backup[/COLOR]'.format(CONFIG.COLOR2), "[COLOR {0}]Would you like to include the [COLOR {1}]guisettings.xml[/COLOR]?[/COLOR]".format( CONFIG.COLOR2, CONFIG.COLOR1), yeslabel="[B][COLOR springgreen]Yes Include[/COLOR][/B]", nolabel="[B][COLOR red]No Continue[/COLOR][/B]"): zipf.write(CONFIG.GUISETTINGS, '/userdata/guisettings.xml', zipfile.ZIP_DEFLATED) except Exception as e: zipf.close() logging.log("[Back Up] Type = theme: {0}".format(str(e))) self.dialog.ok(CONFIG.ADDONTITLE, "[COLOR {0}]{1}[/COLOR][COLOR {2}] theme zip failed:[/COLOR]".format(CONFIG.COLOR1, themename, CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, str(e))) if not tempzipname == '': try: os.remove(tempzipname) except Exception as e: logging.log(str(e)) else: try: os.remove(zipname) except Exception as e: logging.log(str(e)) return zipf.close() if not tempzipname == '': success = xbmcvfs.rename(tempzipname, zipname) if success == 0: xbmcvfs.copy(tempzipname, zipname) xbmcvfs.delete(tempzipname) self.dialog.ok(CONFIG.ADDONTITLE, "[COLOR {0}]{1}[/COLOR][COLOR {2}] theme zip successful:[/COLOR]".format(CONFIG.COLOR1, themename, CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, zipname)) def backup_addon_data(self, name=""): if self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]Are you sure you wish to backup the current addon_data?[/COLOR]".format( CONFIG.COLOR2), nolabel="[B][COLOR red]Cancel Backup[/COLOR][/B]", yeslabel="[B][COLOR springgreen]Backup Addon_Data[/COLOR][/B]"): if name == "": name = tools.get_keyboard("", "Please enter a name for the addon_data zip") if not name: return False name = quote_plus(name) name = '{0}_addondata.zip'.format(name) tempzipname = '' zipname = os.path.join(CONFIG.MYBUILDS, name) try: zipf = zipfile.ZipFile(xbmc.translatePath(zipname), mode='w', allowZip64=True) except: try: tempzipname = os.path.join(CONFIG.PACKAGES, '{0}.zip'.format(name)) zipf = zipfile.ZipFile(tempzipname, mode='w', allowZip64=True) except: logging.log("Unable to create {0}_addondata.zip".format(name), level=xbmc.LOGERROR) if self.dialog.yesno(CONFIG.ADDONTITLE, "[COLOR {0}]We are unable to write to the current backup directory, would you like to change the location?[/COLOR]".format( CONFIG.COLOR2), yeslabel="[B][COLOR springgreen]Change Directory[/COLOR][/B]", nolabel="[B][COLOR red]Cancel[/COLOR][/B]"): CONFIG.open_settings() return else: return for_progress = 0 ITEM = [] tools.convert_special(CONFIG.ADDON_DATA, True) tools.ascii_check(CONFIG.ADDON_DATA, True) self.progress_dialog.create(CONFIG.ADDONTITLE + "[COLOR {0}]: Creating Zip[/COLOR]".format(CONFIG.COLOR2), "[COLOR {0}]Creating back up zip".format(CONFIG.COLOR2), "", "Please Wait...[/COLOR]") for base, dirs, files in os.walk(CONFIG.ADDON_DATA): dirs[:] = [d for d in dirs if d not in CONFIG.EXCLUDE_DIRS] files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: ITEM.append(file) N_ITEM = len(ITEM) bad_files = [ (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.exodusredux', 'cache.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.exodusredux', 'cache.meta.5.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.exodusredux', 'cache.providers.13.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.thecrew', 'cache.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.thecrew', 'cache.meta.5.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.thecrew', 'cache.providers.13.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.scrubsv2', 'cache.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.scrubsv2', 'cache.meta.5.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.scrubsv2', 'cache.providers.13.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.venom', 'cache.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.venom', 'cache.meta.5.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.venom', 'cache.providers.13.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.numbersbynumbers', 'cache.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.numbersbynumbers', 'cache.meta.5.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.numbersbynumbers', 'cache.providers.13.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.gaia', 'cache.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.gaia', 'meta.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.seren', 'cache.db')), (os.path.join(CONFIG.ADDON_DATA, 'plugin.video.seren', 'torrentScrape.db')), (os.path.join(CONFIG.ADDON_DATA, 'script.module.simplecache', 'simplecache.db'))] for base, dirs, files in os.walk(CONFIG.ADDON_DATA): dirs[:] = [d for d in dirs if d not in CONFIG.EXCLUDE_DIRS] files[:] = [f for f in files if f not in CONFIG.EXCLUDE_FILES] for file in files: try: for_progress += 1 progress = tools.percentage(for_progress, N_ITEM) self.progress_dialog.update(int(progress), '[COLOR {0}]Creating back up zip: [COLOR{1}]{2}[/COLOR] / [COLOR{3}]{4}[/COLOR]'.format( CONFIG.COLOR2, CONFIG.COLOR1, for_progress, CONFIG.COLOR1, N_ITEM), '[COLOR {0}]{1}[/COLOR]'.format(CONFIG.COLOR1, file), '') fn = os.path.join(base, file) if file in CONFIG.LOGFILES: logging.log("[Back Up] Type = addon_data: Ignore {0} - Log Files".format(file)) continue elif os.path.join(base, file) in bad_files: logging.log("[Back Up] Type = addon_data: Ignore {0} - Cache Files".format(file)) continue elif os.path.join('addons', 'packages') in fn: logging.log("[Back Up] Type = addon_data: Ignore {0} - Packages Folder".format(file)) continue elif file.endswith('.csv'): logging.log("[Back Up] Type = addon_data: Ignore {0} - CSV File".format(file)) continue elif file.endswith('.db') and 'Database' in base: temp = file.replace('.db', '') temp = ''.join([i for i in temp if not i.isdigit()]) if temp in CONFIG.DB_FILES: if not file == db.latest_db(temp): logging.log("[Back Up] Type = addon_data: Ignore {0} - Database Files".format(file)) continue try: zipf.write(fn, fn[len(CONFIG.ADDON_DATA):], zipfile.ZIP_DEFLATED) except Exception as e: logging.log("[Back Up] Type = addon_data: Unable to backup {0}".format(file)) logging.log("Backup Error: {0}".format(str(e))) except Exception as e: logging.log("[Back Up] Type = addon_data: Unable to backup {0}".format(file)) logging.log("Backup Error: {0}".format(str(e))) zipf.close() if not tempzipname == '': success = xbmcvfs.rename(tempzipname, zipname) if success == 0: xbmcvfs.copy(tempzipname, zipname) xbmcvfs.delete(tempzipname) self.progress_dialog.close() self.dialog.ok(CONFIG.ADDONTITLE, "[COLOR {0}]{1}[/COLOR] [COLOR {2}]backup successful:[/COLOR]".format(CONFIG.COLOR1, name, CONFIG.COLOR2), "[COLOR {0}]{1}[/COLOR]".format(CONFIG.COLOR1, zipname)) def backup(action, name=''): cls = Backup() if action == "addonpack": cls.backup_addon_pack(name) elif action == "build": cls.backup_build(name) elif action == "gui": cls.backup_gui(name) elif action == "theme": cls.backup_theme(name) elif action == "addondata": cls.backup_addon_data(name)