Files
Oli Passey f0bbdc56fb September 2018 Update
Added - Koding AIO to repo as addons depend on it
Updated - DefCon now has 2018 talks in
2018-09-17 19:54:29 +01:00

1076 lines
48 KiB
Python

# -*- coding: utf-8 -*-
# script.module.python.koding.aio
# Python Koding AIO (c) by TOTALREVOLUTION LTD (support@trmc.freshdesk.com)
# Python Koding AIO is licensed under a
# Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
# You should have received a copy of the license along with this
# work. If not, see http://creativecommons.org/licenses/by-nc-nd/4.0.
# Please make sure you've read and understood the license, this code can NOT be used commercially
# and it can NOT be modified and redistributed. If you're found to be in breach of this license
# then any affected add-ons will be blacklisted and will not be able to work on the same system
# as any other add-ons which use this code. Thank you for your cooperation.
import os
import shutil
import os
import sys
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
from systemtools import Last_Error
from xml.etree import ElementTree
dp = xbmcgui.DialogProgress()
dialog = xbmcgui.Dialog()
HOME = 'special://home'
PROFILE = 'special://profile'
DATABASE = os.path.join(PROFILE,'Database')
#----------------------------------------------------------------
# TUTORIAL #
class xml(object):
"""
SETTINGS - CREATE CUSTOM ADD-ON SETTINGS:
All credit goes to OptimusGREEN for this module.
This will create a new settings file for your add-on which you can read and write to. This is separate
to the standard settings.xml and you can call the file whatever you want, however you would presumably
call it something other than settings.xml as that is already used by Kodi add-ons.
CODE: XML(path)
AVAILABLE CLASSES:
ParseValue - This class will allow you to get the value of an item in these custom settings.
SetValue - This class allows you to set a value to the custom settings. If the settings.xml doesn't exist it will be automatically created so long as the path given in XML is writeable.
EXAMPLE CODE:
myXmlFile = "special://userdata/addon_data/script.module.python.koding.aio/timefile.xml"
timefile = koding.xml(myXmlFile)
getSetting = timefile.ParseValue
setSetting = timefile.SetValue
dialog.ok('CHECK SETTINGS','If you take a look in the addon_data folder for python koding a new file called timefile.xml will be created when you click OK.')
setSetting("autorun", "true")
autoRun = getSetting("autorun")
dialog.ok('AUTORUN VALUE','The value of autorun in these new settings is [COLOR dodgerblue]%s[/COLOR].[CR][CR]Press OK to delete this file.'%autoRun)
os.remove(koding.Physical_Path(myXmlFile))
~"""
def __init__(self, xmlFile, masterTag="settings", childTag="setting"):
self.xmlFile = xmlFile
self.masterTag = masterTag
self.childTag = childTag
self.xmlFile = Physical_Path(self.xmlFile)
def ParseValue(self, settingID, settingIDTag="id", settingValueTag="value", addChild=False, formatXML=True):
if not os.path.exists(self.xmlFile):
return
tree = ElementTree.parse(self.xmlFile)
root = tree.getroot()
for child in root:
if child.attrib[settingIDTag] == settingID:
return child.attrib.get(settingValueTag)
def SetValue(self, settingID, newValue, settingIDTag="id", settingValueTag="value", addChild=False, asString=False, formatXML=True):
if not os.path.exists(self.xmlFile):
self.CreateXML(settingIDTag=settingIDTag, settingValueTag=settingValueTag, addChild=addChild, formatXML=formatXML)
tree = ElementTree.parse(self.xmlFile)
root = tree.getroot()
targetChild = None
for child in root:
if child.attrib[settingIDTag] == settingID:
targetChild = child
if targetChild is None:
self.AppendChild(root, settingID=settingID, newValue=newValue, settingIDTag=settingIDTag, settingValueTag=settingValueTag)
else:
for child in root:
if child.attrib[settingIDTag] == settingID:
child.attrib['%s' % (settingValueTag)] = '%s' % (newValue)
tree.write(self.xmlFile)
if asString:
readfile = open(self.xmlFile, 'r')
content = readfile.read()
readfile.close()
pretty = self.Prettify(content, asString=True)
else:
pretty = self.Prettify(self.xmlFile)
f = open(self.xmlFile, "w")
f.write(pretty)
f.close()
def CreateXML(self, settingIDTag="id", settingValueTag="value", addChild=False, formatXML=True):
root = ElementTree.Element("%s" % self.masterTag)
if addChild:
sub = ElementTree.SubElement(root, "%s" % self.childTag)
sub.set(settingIDTag, "")
sub.set(settingValueTag, "")
tree = ElementTree.ElementTree(root)
tree.write(self.xmlFile)
if formatXML:
pretty = self.Prettify(self.xmlFile)
f = open(self.xmlFile, "w")
f.write(pretty)
f.close()
def AppendChild(self, root, settingID, newValue, settingIDTag="id", settingValueTag="value"):
ElementTree.SubElement(root, self.childTag, attrib={settingIDTag: settingID, settingValueTag: newValue})
return root
def Prettify(self, elem, asString=False):
import xml.dom.minidom
if asString:
xml = xml.dom.minidom.parseString(elem)
pretty_xml_as_string = '\n'.join([line for line in xml.toprettyxml(indent=' ' * 2).split('\n') if line.strip()])
else:
pretty_xml_as_string = '\n'.join([line for line in xml.dom.minidom.parse(open(elem)).toprettyxml(indent=' ' * 2).split('\n') if line.strip()])
return pretty_xml_as_string
#----------------------------------------------------------------
# Legacy code, now use new function Compress
def Archive_Tree(sourcefile, destfile, exclude_dirs=['temp'], exclude_files=['kodi.log','kodi.old.log','xbmc.log','xbmc.old.log','spmc.log','spmc.old.log'], message_header = 'ARCHIVING', message = 'Creating archive'):
Compress(src=sourcefile, dst=destfile, exclude_dirs=exclude_dirs, exclude_files=exclude_files)
#----------------------------------------------------------------
# TUTORIAL #
def Compress(src,dst,compression='zip',parent=False, exclude_dirs=['temp'], exclude_files=['kodi.log','kodi.old.log','xbmc.log','xbmc.old.log','spmc.log','spmc.old.log'], message_header = 'ARCHIVING', message = 'Creating archive'):
"""
Compress files in either zip or tar format. This will most likely be replacing
Archive_Tree longer term as this has better functionality but it's currently
missing the custom message and exclude files options.
IMPORTANT: There was a known bug where some certain compressed tar.gz files can cause the system to hang
and a bad zipfile will continue to be made until it runs out of space on your storage device. In the unlikely
event you encounter this issue just add the problematic file(s) to your exclude list. I think this has since
been fixed since a complete re-code to this function, or at least I've been unable to recreate it. If you
find this problem is still occuring please let me know on the forum at http://totalrevolution.tv/forum
(user: trevdev), thankyou.
CODE: Compress(src,dst,[compression,parent])
AVAILABLE PARAMS:
(*) src - This is the source folder you want to compress
(*) dst - This is the destination file you want to create
compression - By default this is set to 'zip' but you can also use 'tar'
parent - By default this is set to False which means it will compress
everything inside the path given. If set to True it will do the same but
it will include the parent folder name - ideal if you want to zip up
an add-on folder and be able to install via Kodi Settings.
exclude_dirs - This is optional, if you have folder names you want to exclude just
add them here as a list item. By default the folder 'temp' is added to this list so
if you need to include folders called temp make sure you send through a list, even
if it's an empty one. The reason for leaving temp out is that's where Kodi logfiles
and crashlogs are stored on a lot of devices and these are generally not needed in
backup zips.
exclude_files - This is optional, if you have specific file names you want to
exclude just add them here as a list item. By default the list consists of:
'kodi.log','kodi.old.log','xbmc.log','xbmc.old.log','spmc.log','spmc.old.log'
EXAMPLE CODE:
koding_path = koding.Physical_Path('special://home/addons/script.module.python.koding.aio')
zip_dest = koding.Physical_Path('special://home/test_addon.zip')
zip_dest2 = koding.Physical_Path('special://home/test_addon2.zip')
tar_dest = koding.Physical_Path('special://home/test_addon.tar')
tar_dest2 = koding.Physical_Path('special://home/test_addon2.tar')
koding.Compress(src=koding_path,dst=zip_dest,compression='zip',parent=True)
koding.Compress(src=koding_path,dst=zip_dest2,compression='zip',parent=False)
koding.Compress(src=koding_path,dst=tar_dest,compression='tar',parent=True)
koding.Compress(src=koding_path,dst=tar_dest2,compression='tar',parent=False)
koding.Text_Box('CHECK HOME FOLDER','If you check your Kodi home folder you should now have 4 different compressed versions of the Python Koding add-on.\n\ntest_addon.zip: This has been zipped up with parent set to True\n\ntest_addon2.zip: This has been zipped up with parent set to False.\n\ntest_addon.tar: This has been compressed using tar format and parent set to True\n\ntest_addon2.tar: This has been compressed using tar format and parent set to False.\n\nFeel free to manually delete these.')
~"""
import zipfile
import tarfile
directory = os.path.dirname(dst)
if not os.path.exists(directory):
try:
os.makedirs(directory)
except:
dialog.ok('ERROR','The destination directory you gave does not exist and it wasn\'t possible to create it.')
return
if compression == 'zip':
zip = zipfile.ZipFile(dst, 'w', compression=zipfile.ZIP_DEFLATED)
elif compression == 'tar':
zip = tarfile.open(dst, mode='w')
module_id = 'script.module.python.koding.aio'
this_module = xbmcaddon.Addon(id=module_id)
folder_size = Folder_Size(src,'mb')
available_space = Free_Space(HOME,'mb')
if os.path.exists(src):
choice = True
if float(available_space) < float(folder_size):
choice = dialog.yesno(this_module.getLocalizedString(30809), this_module.getLocalizedString(30810), this_module.getLocalizedString(30811) % folder_size, this_module.getLocalizedString(30812) % available_space, yeslabel = this_module.getLocalizedString(30813), nolabel = this_module.getLocalizedString(30814))
if choice:
root_len = len(os.path.dirname(os.path.abspath(src)))
for base, dirs, files in os.walk(src):
dirs[:] = [d for d in dirs if d not in exclude_dirs]
files[:] = [f for f in files if f not in exclude_files and not 'crashlog' in f and not 'stacktrace' in f]
archive_root = os.path.abspath(base)[root_len:]
for f in files:
fullpath = os.path.join(base, f)
if parent:
archive_name = os.path.join(archive_root, f)
if compression == 'zip':
zip.write(fullpath, archive_name, zipfile.ZIP_DEFLATED)
elif compression == 'tar':
zip.add(fullpath, archive_name)
else:
newpath = fullpath.split(src)[1]
if compression == 'zip':
zip.write(fullpath, newpath, zipfile.ZIP_DEFLATED)
elif compression == 'tar':
zip.add(fullpath, newpath)
zip.close()
#----------------------------------------------------------------
# TUTORIAL #
def Create_Paths(path=''):
"""
Send through a path to a file, if the directories required do not exist this will create them.
CODE: Create_Paths(path)
AVAILABLE PARAMS:
(*) path - This is the full path including the filename. The path
sent through will be split up at every instance of '/'
EXAMPLE CODE:
my_path = xbmc.translatePath('special://home/test/testing/readme.txt')
koding.Create_Paths(path=my_path)
dialog.ok('PATH CREATED','Check in your Kodi home folder and you should now have sub-folders of /test/testing/.','[COLOR=gold]Press ok to remove these folders.[/COLOR]')
shutil.rmtree(xbmc.translatePath('special://home/test'))
~"""
home_path = Physical_Path('special://home')
path = path.replace(home_path,'')
newpath = os.path.join('special://home',path)
if path != '' and not os.path.exists(Physical_Path(newpath)):
root_path = path.split(os.sep)
if root_path[-1] == '':
root_path.pop()
root_path.pop()
final_path = ''
for item in root_path:
final_path = os.path.join(final_path,item)
final_path = os.path.join('special://home',final_path)
xbmcvfs.mkdirs(final_path)
#----------------------------------------------------------------
# TUTORIAL #
def DB_Path_Check(db_path):
"""
If you need to find out the current "real" database in use then this is the function for you.
It will scan for a specific database type (e.g. addons) and return the path to the one which was last updated.
This is particularly useful if the system has previously updated to a newer version rather than a fresh install
or if they've installed a "build" which contained old databases.
CODE: DB_Path_Check(db_path)
AVAILABLE VALUES:
(*) db_path - This is the string the database starts with.
If you want to find the path for the addons*.db you would use "addons"
as the value, if you wanted to find the path of the MyVideos*.db you would use
"myvideos" etc. - it is not case sensitive.
EXAMPLE CODE:
dbpath = koding.DB_Path_Check(db_path='addons')
dialog.ok('ADDONS DB','The path to the current addons database is:',dbpath)
~"""
finalfile = 0
dirs,databasepath = xbmcvfs.listdir(DATABASE)
for item in databasepath:
if item.lower().endswith('.db') and item.lower().startswith(db_path.lower()):
mydb = os.path.join(DATABASE,item)
lastmodified = xbmcvfs.Stat(mydb).st_mtime()
if lastmodified>finalfile:
finalfile = lastmodified
gooddb = mydb
return Physical_Path(gooddb)
#---------------------------------------------------------------------------------------------------
# TUTORIAL #
def Delete_Crashlogs(extra_paths=[]):
"""
Delete all kodi crashlogs. This function will retun the amount of successfully removed crashlogs.
CODE: Delete_Crashlogs([extra_paths])
AVAILABLE PARAMS:
extra_paths - By default this will search for crashlogs for xbmc,
kodi and spmc. If you want to add compatibility for other forks of
Kodi please send through a list of the files you want deleted. The
format to use needs to be like example shown below.
EXAMPLE CODE:
# Lets setup some extra crashlog types for tvmc and ftmc kodi forks
log_path = xbmc.translatePath('special://logpath/')
tvmc_path = os.path.join(log_path,'tvmc_crashlog*.*')
ftmc_path = os.path.join(log_path,'ftmc_crashlog*.*')
deleted_files = koding.Delete_Crashlogs(extra_paths=[tvmc_path, ftmc_path])
if deleted_files > 0:
dialog.ok('CRASHLOGS DELETED','Congratulations, a total of %s crashlogs have been deleted.')
else:
dialog.ok('NO CRASHLOGS','No crashlogs could be found on the system.')
~"""
import glob
log_path = 'special://logpath/'
xbmc_path = (os.path.join(log_path, 'xbmc_crashlog*.*'))
kodi_path = (os.path.join(log_path, 'kodi_crashlog*.*'))
spmc_path = (os.path.join(log_path, 'spmc_crashlog*.*'))
paths = [xbmc_path, kodi_path, spmc_path]
total = 0
for items in paths:
for file in glob.glob(items):
try:
xbmcvfs.delete(file)
total+=1
except:
pass
return total
#----------------------------------------------------------------
# TUTORIAL #
def Delete_Files(filepath = HOME, filetype = '*.txt', subdirectories=False):
"""
Delete all specific filetypes in a path (including sub-directories)
CODE: Delete_Files([filepath, filetype, subdirectories])
AVAILABLE PARAMS:
(*) filepath - By default this points to the Kodi HOME folder (special://home).
The path you send through must be a physical path and not special://
(*) filetype - The type of files you want to delete, by default it's set to *.txt
subdirectories - By default it will only search the folder given, if set to True
all filetypes listed above will be deleted in the sub-directories too.
WARNING: This is an extremely powerful and dangerous tool! If you wipe your whole system
by putting in the wrong path then it's your own stupid fault!
EXAMPLE CODE:
delete_path = 'special://profile/addon_data/test'
xbmcvfs.mkdirs(delete_path)
test1 = os.path.join(delete_path,'test1.txt')
test2 = os.path.join(delete_path,'test2.txt')
koding.Text_File(test1,'w','testing1')
koding.Text_File(test2,'w','testing2')
dialog.ok('DELETE FILES','All *.txt files will be deleted from:', '', '/userdata/addon_data/test/')
koding.Delete_Files(filepath=delete_path, filetype='.txt', subdirectories=True)
~"""
filepath = Physical_Path(filepath)
if filepath == '/' or filepath == '.' or filepath == '' or (filepath[1]==':' and len(filepath)<4):
dialog.ok('IDTenT ERROR!!!','You are trying to wipe your whole system!!!','Be more careful in future, not everyone puts checks like this in their code!')
return
if os.path.exists(filepath):
filetype = filetype.replace('*','')
if subdirectories:
for parent, dirnames, filenames in os.walk(filepath):
for fn in filenames:
if fn.lower().endswith(filetype):
xbmcvfs.delete(os.path.join(parent, fn))
else:
for delete_file in xbmcvfs.listdir(filepath):
delete_path = os.path.join(filepath,delete_file)
if delete_path.endswith(filetype):
try:
xbmcvfs.delete(delete_path)
except:
xbmc.log(Last_Error(),2)
else:
xbmc.log('### Cannot delete files as directory does not exist: %s' % filepath,2)
#----------------------------------------------------------------
# TUTORIAL #
def Delete_Folders(filepath='', ignore=[]):
"""
Completely delete a folder and all it's sub-folders. With the ability to add
an ignore list for any folders/files you don't want removed.
CODE: Delete_Folders(filepath, [ignore])
AVAILABLE PARAMS:
(*) filepath - Use the physical path you want to remove, this must be converted
to the physical path and will not work with special://
ignore - A list of paths you want to ignore. These need to be sent
through as physical paths so just use koding.Physical_Path() when creating
your list and these can be folder paths or filepaths.
WARNING: This is an extremely powerful and dangerous tool! If you wipe important
system files from your system by putting in the wrong path then I'm afraid that's
your own stupid fault! A check has been put in place so you can't accidentally
wipe the whole root.
EXAMPLE CODE:
delete_path = koding.Physical_Path('special://profile/py_koding_test')
# Create new test directory to remove
if not os.path.exists(delete_path):
os.makedirs(delete_path)
# Fill it with some dummy files
file1 = os.path.join(delete_path,'file1.txt')
file2 = os.path.join(delete_path,'file2.txt')
file3 = os.path.join(delete_path,'file3.txt')
koding.Dummy_File(dst=file1, size=10, size_format='kb')
koding.Dummy_File(dst=file2, size=10, size_format='kb')
koding.Dummy_File(dst=file3, size=10, size_format='kb')
dialog.ok('TEST FILE CREATED','If you look in your userdata folder you should now see a new test folder containing 3 dummy files. The folder name is \'py_koding_test\'.')
if dialog.yesno('[COLOR gold]DELETE FOLDER[/COLOR]','Everything except file1.txt will now be removed from:', '/userdata/py_koding_test/','Do you want to continue?'):
koding.Delete_Folders(filepath=delete_path, ignore=[file1])
dialog.ok('DELETE LEFTOVERS','When you press OK we will delete the whole temporary folder we created including it\'s contents')
koding.Delete_Folders(filepath=delete_path)
~"""
exclude_list = ['','/','\\','C:/','storage']
# Check you're not trying to wipe root!
if filepath in exclude_list:
dialog.ok('FILEPATH REQUIRED','You\'ve attempted to remove files but forgot to pass through a valid filepath. Luckily this failsafe check is in place or you could have wiped your whole system!')
# If there's some ignore files we run through deleting everything but those files
elif len(ignore) > 0:
for root, dirs, files in os.walk(filepath, topdown=False):
cont = True
if not root in ignore:
for item in ignore:
if item in root:
cont=False
break
if cont:
for file in files:
file_path = os.path.join(root,file)
if file_path not in ignore:
try:
xbmcvfs.delete(file_path)
except:
xbmc.log('Failed to delete: %s'%file_path,2)
if len(os.listdir(root)) == 0:
try:
xbmcvfs.rmdir(root)
except:
pass
# If a simple complete wipe of a directory and all sub-directories is required we use this
elif os.path.exists(filepath):
shutil.rmtree(filepath, ignore_errors=True)
# xbmc.executebuiltin('Container.Refresh')
#----------------------------------------------------------------
# TUTORIAL #
def Dummy_File(dst= 'special://home/dummy.txt', size='10', size_format='mb'):
"""
Create a dummy file in whatever location you want and with the size you want.
Use very carefully, this is designed for testing purposes only. Accidental
useage can result in the devices storage becoming completely full in just a
few seconds. If using a cheap poor quality device (like many android units)
then you could even end up killing the device as some of them are made
with very poor components which are liable to irreversable corruption.
CODE: koding.Dummy_File(dest, [size, size_format])
AVAILABLE PARAMS:
dst - This is the destination folder. This needs to be a FULL path including
the file extension. By default this is set to special://home/dummy.txt
size - This is an optional integer, by default a file of 10 MB will be created.
size_format - By default this is set to 'mb' (megabytes) but you can change this to
'b' (bytes), 'kb' (kilobytes), 'gb' (gigabytes)
EXAMPLE CODE:
dummy = 'special://home/test_dummy.txt'
koding.Dummy_File(dst=dummy, size=100, size_format='b')
dialog.ok('DUMMY FILE CREATED','Check your Kodi home folder and you should see a 100 byte test_dummy.txt file.','[COLOR=gold]Press OK to delete this file.[/COLOR]')
xbmcvfs.delete(dummy)
~"""
dst = Physical_Path(dst)
xbmc.log('dst: %s'%dst,2)
if size_format == 'kb':
size = float(size*1024)
elif size_format == 'mb':
size = float(size*1024) * 1024
elif size_format == 'gb':
size = float(size*1024) * 1024 * 1024
xbmc.log('format: %s size: %s'%(size_format, size), 2)
f = open(dst,"wb")
f.seek(size-1)
f.write("\0")
f.close()
#----------------------------------------------------------------
# TUTORIAL #
def End_Path(path):
"""
Split the path at every '/' and return the final file/folder name.
If your path uses backslashes rather than forward slashes it will use
that as the separator.
CODE: End_Path(path)
AVAILABLE PARAMS:
path - This is the path where you want to grab the end item name.
EXAMPLE CODE:
addons_path = 'special://home/addons'
file_name = koding.End_Path(path=addons_path)
dialog.ok('ADDONS FOLDER','Path checked:',addons_path,'Folder Name: [COLOR=dodgerblue]%s[/COLOR]'%file_name)
file_path = 'special://home/addons/script.module.python.koding.aio/addon.xml'
file_name = koding.End_Path(path=file_path)
dialog.ok('FILE NAME','Path checked:',file_path,'File Name: [COLOR=dodgerblue]%s[/COLOR]'%file_name)
~"""
if '/' in path:
path_array = path.split('/')
if path_array[-1] == '':
path_array.pop()
elif '\\' in path:
path_array = path.split('\\')
if path_array[-1] == '':
path_array.pop()
else:
return path
return path_array[-1]
#----------------------------------------------------------------
# TUTORIAL #
def Extract(_in, _out, dp=None, show_error=False):
"""
This function will extract a zip or tar file and return true or false so unlike the
builtin xbmc function "Extract" this one will pause code until it's completed the action.
CODE: koding.Extract(src,dst,[dp])
dp is optional, by default it is set to false
AVAILABLE PARAMS:
(*) src - This is the source file, the actual zip/tar. Make sure this is a full path to
your zip file and also make sure you're not using "special://". This extract function
is only compatible with .zip/.tar/.tar.gz files
(*) dst - This is the destination folder, make sure it's a physical path and not
"special://...". This needs to be a FULL path, if you want it to extract to the same
location as where the zip is located you still have to enter the full path.
dp - This is optional, if you pass through the dp function as a DialogProgress()
then you'll get to see the status of the extraction process. If you choose not to add
this paramater then you'll just get a busy spinning circle icon until it's completed.
See the example below for a dp example.
show_error - By default this is set to False, if set to True an error dialog
will appear showing details of the file which failed to extract.
EXAMPLE CODE:
koding_path = koding.Physical_Path('special://home/addons/script.module.python.koding.aio')
zip_dest = koding.Physical_Path('special://home/test_addon.zip')
extract_dest = koding.Physical_Path('special://home/TEST')
koding.Compress(src=koding_path,dst=zip_dest,compression='zip',parent=True)
dp = xbmcgui.DialogProgress()
dp.create('Extracting Zip','Please Wait')
if koding.Extract(_in=zip_dest,_out=extract_dest,dp=dp,show_error=True):
dialog.ok('YAY IT WORKED!','We just zipped up your python koding add-on then extracted it to a new folder in your Kodi root directory called TEST. Press OK to delete these files.')
xbmcvfs.delete(zip_dest)
shutil.rmtree(extract_dest)
else:
dialog.ok('BAD NEWS!','UH OH SOMETHING WENT HORRIBLY WRONG')
~"""
import tarfile
import xbmcaddon
import zipfile
module_id = 'script.module.python.koding.aio'
this_module = xbmcaddon.Addon(id=module_id)
nFiles = 0
count = 0
if xbmcvfs.exists(_in):
if zipfile.is_zipfile(_in):
zin = zipfile.ZipFile(_in, 'r')
nFiles = float(len(zin.infolist()))
contents = zin.infolist()
elif tarfile.is_tarfile(_in):
zin = tarfile.open(_in)
contents = [tarinfo for tarinfo in zin.getmembers()]
nFiles = float(len(contents))
if nFiles > 0:
if dp:
try:
for item in contents:
count += 1
update = count / nFiles * 100
dp.update(int(update))
zin.extract(item, _out)
zin.close()
return True
except:
xbmc.log(Last_Error(),2)
return False
else:
try:
zin.extractall(_out)
return True
except:
xbmc.log(Last_Error(),2)
return False
else:
xbmc.log('NOT A VALID ZIP OR TAR FILE: %s' % _in,2)
else:
if show_error:
dialog.ok(this_module.getLocalizedString(30965),this_module.getLocalizedString(30815) % _in)
#----------------------------------------------------------------
# TUTORIAL #
def Free_Space(dirname = HOME, filesize = 'b'):
"""
Show the amount of available free space in a path, this can be returned in a number of different formats.
CODE: Free_Space([dirname, filesize])
AVAILABLE PARAMS:
dirname - This optional, by default it will tell you how much space is available in your special://home
folder. If you require information for another path (such as a different partition or storage device)
then enter the physical path. This currently only works for local paths and not networked drives.
filesize - By default you'll get a return of total bytes, however you can get the value as bytes,
kilobytes, megabytes, gigabytes and terabytes..
VALUES:
'b' = bytes (integer)
'kb' = kilobytes (float to 1 decimal place)
'mb' = kilobytes (float to 2 decimal places)
'gb' = kilobytes (float to 3 decimal places)
'tb' = terabytes (float to 4 decimal places)
EXAMPLE CODE:
HOME = Physical_Path('special://home')
my_space = koding.Free_Space(HOME, 'gb')
dialog.ok('Free Space','Available space in HOME: %s GB' % my_space)
~"""
import ctypes
dirname = Physical_Path(dirname)
filesize = filesize.lower()
if xbmc.getCondVisibility('system.platform.windows'):
free_bytes = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(dirname), None, None, ctypes.pointer(free_bytes))
finalsize = free_bytes.value
else:
st = os.statvfs(dirname)
finalsize = st.f_bavail * st.f_frsize
if filesize == 'b':
return finalsize
elif filesize == 'kb':
return "%.1f" % (float(finalsize / 1024))
elif filesize == 'mb':
return "%.2f" % (float(finalsize / 1024) / 1024)
elif filesize == 'gb':
return "%.3f" % (float(finalsize / 1024) / 1024 / 1024)
elif filesize == 'tb':
return "%.4f" % (float(finalsize / 1024) / 1024 / 1024 / 1024)
#----------------------------------------------------------------
# TUTORIAL #
def Folder_Size(dirname = HOME, filesize = 'b'):
"""
Return the size of a folder path including sub-directories,
this can be returned in a number of different formats.
CODE: koding.Folder_Size([dirname, filesize])
AVAILABLE PARAMS:
dirname - This optional, by default it will tell you how much space is available in your
special://home folder. If you require information for another path (such as a different
partition or storage device) then enter the physical path. This currently only works for
local paths and not networked drives.
filesize - By default you'll get a return of total bytes, however you can get the value as
bytes, kilobytes, megabytes, gigabytes and terabytes..
VALUES:
'b' = bytes (integer)
'kb' = kilobytes (float to 1 decimal place)
'mb' = kilobytes (float to 2 decimal places)
'gb' = kilobytes (float to 3 decimal places)
'tb' = terabytes (float to 4 decimal places)
EXAMPLE CODE:
HOME = Physical_Path('special://home')
home_size = Folder_Size(HOME, 'mb')
dialog.ok('Folder Size','KODI HOME: %s MB' % home_size)
~"""
finalsize = 0
for dirpath, dirnames, filenames in os.walk(dirname):
for f in filenames:
fp = os.path.join(dirpath, f)
finalsize += os.path.getsize(fp)
if filesize == 'b':
return finalsize
elif filesize == 'kb':
return "%.1f" % (float(finalsize / 1024))
elif filesize == 'mb':
return "%.2f" % (float(finalsize / 1024) / 1024)
elif filesize == 'gb':
return "%.3f" % (float(finalsize / 1024) / 1024 / 1024)
elif filesize == 'tb':
return "%.4f" % (float(finalsize / 1024) / 1024 / 1024 / 1024)
#----------------------------------------------------------------
# TUTORIAL #
def Fresh_Install(keep_addons=[],ignore=[],keepdb=True):
"""
Attempt to completely wipe your install. You can send through a list
of addons or paths you want to ignore (leave in the setup) or you can
leave blank. If left blank and the platform is OpenELEC or LibreELEC
it will perform a hard reset command followed by a reboot.
CODE: Fresh_Install([keep_addons, ignore, keepdb)
AVAILABLE PARAMS:
keep_addons - This is optional, if you have specific add-ons you want to omit
from the wipe (leave intact) then just enter a list of add-on id's here. The code
will determine from the addon.xml file which dependencies and sub-dependencies are
required for that add-on so there's no need to create a huge list, you only need to
list the master add-on id's. For example if you want to keep the current skin and
your add-on you would use: keep_addons=['plugin.program.myaddon',System('currentskin')]
and all addons/dependencies associated with those two add-ons will be added to the ignore
list.
ignore - This is optional, you can send through a list of paths you want to omit from
the wipe. You can use folder paths to skip the whole folder or you can use individual
file paths. Please make sure you use the physical path and not special://
So before creating your list make sure you use xbmc.translatePath()
keepdb - By default this is set to True which means the code will keep all the Kodi databases
intact and perform a profile reload once wipe is complete. This will mean addons, video, music,
epg, ADSP and viewtypes databases will remain completely untouched and Kodi should be fine to use
without the need for a restart. If you set keepdb to False nothing will happen once the wipe has
completed and it's up to you to choose what to do in your main code. I would highly recommend an
ok dialog followed by xbmc.executebuiltin('Quit'). This will force Kodi to recreate all the relevant
databases when they re-open. If you try and continue using Kodi without restarting the databases
will not be recreated and you risk corruption.
EXAMPLE CODE:
if dialog.yesno('[COLOR gold]TOTAL WIPEOUT![/COLOR]','This will attempt give you a totally fresh install of Kodi.','Are you sure you want to continue?'):
if dialog.yesno('[COLOR gold]FINAL CHANCE!!![/COLOR]','If you click Yes this WILL attempt to wipe your install', '[COLOR=dodgerblue]ARE YOU 100% CERTAIN YOU WANT TO WIPE?[/COLOR]'):
clean_state = koding.Fresh_Install()
~"""
# If it's LE/OE and there are no files to ignore we do a hard reset
from systemtools import Cleanup_Textures
if ( len(ignore)==0 ) and ( len(keep_addons)==0 ) and ( xbmc.getCondVisibility("System.HasAddon(service.libreelec.settings)") or xbmc.getCondVisibility("System.HasAddon(service.openelec.settings)") ):
xbmc.log('OE DETECTED',2)
resetpath='storage/.cache/reset_oe'
Text_File(resetpath,'w')
xbmc.executebuiltin('reboot')
else:
from addons import Dependency_Check
xbmc.log('DOING MAIN WIPE',2)
skip_array = []
addonsdb = DB_Path_Check('addons')
textures = DB_Path_Check('Textures')
Cleanup_Textures(frequency=1,use_count=999999)
if len(keep_addons) > 0:
ignorelist = Dependency_Check(addon_id = keep_addons, recursive = True)
for item in ignorelist:
skip_array.append(xbmcaddon.Addon(id=item).getAddonInfo('path'))
skip_array.append(addonsdb)
skip_array.append(textures)
if keepdb:
try:
skip_array.append( DB_Path_Check('Epg') )
except:
xbmc.log('No EPG DB Found, skipping',2)
try:
skip_array.append( DB_Path_Check('MyVideos') )
except:
xbmc.log('No MyVideos DB Found, skipping',2)
try:
skip_array.append( DB_Path_Check('MyMusic') )
except:
xbmc.log('No MyMusic DB Found, skipping',2)
try:
skip_array.append( DB_Path_Check('TV') )
except:
xbmc.log('No TV DB Found, skipping',2)
try:
skip_array.append( DB_Path_Check('ViewModes') )
except:
xbmc.log('No ViewModes DB Found, skipping',2)
try:
skip_array.append( DB_Path_Check('ADSP') )
except:
xbmc.log('No ADSP DB Found, skipping',2)
for item in ignore:
skip_array.append(item)
Delete_Folders(filepath=HOME, ignore=skip_array)
Refresh()
if keepdb:
Refresh('profile')
# Good option for wiping android data but not so good if using the app as a launcher!
# elif xbmc.getCondVisibility('System.Platform.Android'):
# import subprocess
# running = Running_App()
# cleanwipe = subprocess.Popen(['exec ''pm clear '+str(running)+''], executable='/system/bin/sh', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, preexec_fn=Get_ID(setid=True)).communicate()[0]
#----------------------------------------------------------------
# TUTORIAL #
def Get_Contents(path,folders=True,subfolders=False,exclude_list=[],full_path=True,filter=''):
"""
Return a list of either files or folders in a given path.
CODE: Get_Contents(path, [folders, subfolders, exclude_list, full_path, filter])
AVAILABLE PARAMS:
(*) path - This is the path you want to search, no sub-directories are scanned.
folders - By default this is set to True and the returned list will only
show folders. If set to False the returned list will show files only.
exclude_list - Optionally you can add a list of items you don't want returned
full_path - By default the entries in the returned list will contain the full
path to the folder/file. If you only want the file/folder name set this to False.
subfolders - By default this is set to False but if set to true it will check
all sub-directories and not just the directory sent through.
filter - If you want to only return files ending in a specific string you
can add details here. For example to only show '.xml' files you would send
through filter='.xml'.
EXAMPLE CODE:
ADDONS = Physical_Path('special://home/addons')
addon_folders = koding.Get_Contents(path=ADDONS, folders=True, exclude_list=['packages','temp'], full_path=False)
results = ''
for item in addon_folders:
results += 'FOLDER: [COLOR=dodgerblue]%s[/COLOR]\n'%item
koding.Text_Box('ADDON FOLDERS','Below is a list of folders found in the addons folder (excluding packages and temp):\n\n%s'%results)
~"""
final_list = []
path = Physical_Path(path)
# Check all items in the given path
if not subfolders:
dirs,files = xbmcvfs.listdir(path)
if folders:
active_list = dirs
else:
active_list = files
for item in active_list:
if item not in exclude_list:
if full_path:
final_list.append(os.path.join(path,item))
else:
final_list.append(item)
# Traverse through all subfolders
else:
path = Physical_Path(path)
for root, dirnames, filenames in os.walk(path):
if not folders:
for filename in filenames:
file_path = os.path.join(root, filename)
if filter=='':
if full_path:
final_list.append(file_path)
else:
final_list.append(filename)
elif file_path.endswith(filter):
if full_path:
final_list.append(file_path)
else:
final_list.append(filename)
else:
for dirname in dirnames:
if full_path:
final_list.append(os.path.join(root, dirname))
else:
final_list.append(dirname)
return final_list
#----------------------------------------------------------------
# TUTORIAL #
def Move_Tree(src, dst, dp=None):
"""
Move a directory including all sub-directories to a new location.
This will automatically create the new location if it doesn't already
exist and it wierwrite any existing entries if they exist.
CODE: koding.Move_Tree(src, dst)
AVAILABLE PARAMS:
(*) src - This is source directory that you want to copy
(*) dst - This is the destination location you want to copy a directory to.
dp - This is optional, if you pass through the dp function as a DialogProgress()
then you'll get to see the status of the move process. See the example below for a dp example.
EXAMPLE CODE:
dp = xbmcgui.DialogProgress()
source = koding.Physical_Path('special://profile/move_test')
# Lets create a 500MB dummy file so we can move and see dialog progress
dummy = os.path.join(source,'dummy')
if not os.path.exists(source):
os.makedirs(source)
koding.Dummy_File(dst=dummy+'1.txt', size=10, size_format='mb')
koding.Dummy_File(dst=dummy+'2.txt', size=10, size_format='mb')
koding.Dummy_File(dst=dummy+'3.txt', size=10, size_format='mb')
koding.Dummy_File(dst=dummy+'4.txt', size=10, size_format='mb')
koding.Dummy_File(dst=dummy+'5.txt', size=10, size_format='mb')
koding.Dummy_File(dst=dummy+'6.txt', size=10, size_format='mb')
dialog.ok('DUMMY FILE CREATED','If you want to check in your userdata folder you should have a new folder called "move_test" which has 6x 10MB dummy files.')
# This is optional but if you want to see a dialog progress then you'll need this
dp.create('MOVING FILES','Please Wait')
destination = koding.Physical_Path('special://home/My MOVED Dummy File')
koding.Move_Tree(source, destination, dp)
dialog.ok('CHECK YOUR KODI HOME FOLDER','Please check your Kodi home folder, the dummy file should now have moved in there. When you press OK it will be removed')
shutil.rmtree(destination)
~"""
src = Physical_Path(src)
dst = Physical_Path(dst)
if dp:
totalfiles = 0
for root, dirs, files in os.walk(src):
totalfiles += len(files)
count = 0
for src_dir, dirs, files in os.walk(src):
dst_dir = src_dir.replace(src, dst, 1)
if os.path.exists(dst_dir) and not os.path.isdir(dst_dir):
try:
os.remove(dst_dir)
except:
xbmc.log('File with same name as folder exists, need to manually delete:',2)
xbmc.log(dst_dir,2)
if not os.path.exists(dst_dir):
os.makedirs(dst_dir)
for file_ in files:
src_file = os.path.join(src_dir, file_)
dst_file = os.path.join(dst_dir, file_)
if os.path.exists(dst_file) and dst_file != dst:
os.remove(dst_file)
try:
os.rename(src_file,dst_file)
except:
shutil.move(src_file, dst_dir)
if dp:
try:
count += 1
update = count / totalfiles * 100
dp.update(int(update))
except:
pass
try:
shutil.rmtree(src)
except:
pass
if dp:
dp.close()
#----------------------------------------------------------------
# TUTORIAL #
def Physical_Path(path='special://home'):
"""
Send through a special:// path and get the real physical path returned.
This has been written due to the problem where if you're running the Windows Store
version of Kodi the built-in xbmc.translatePath() function is returning bogus results
making it impossible to access databases.
CODE: koding.Physical_Path([path])
AVAILABLE PARAMS:
path - This is the path to the folder/file you want returned. This is optional,
if you leave this out it will just return the path to the root directory (special://home)
EXAMPLE CODE:
location = 'special://home/addons/script.module.python.koding.aio'
real_location = koding.Physical_Path(location)
xbmc.log(real_location,2)
dialog.ok('PHYSICAL PATH','The real path of special://home/addons/script.module.python.koding.aio is:','[COLOR=dodgerblue]%s[/COLOR]'%real_location)
~"""
xbmc_install = xbmc.translatePath('special://xbmc')
if not "WindowsApps" in xbmc_install:
clean = xbmc.translatePath(path)
if sys.platform == 'win32':
clean = clean.replace('\/','\\')
else:
clean = xbmc.translatePath(path)
if clean.startswith(xbmc_install):
if sys.platform == 'win32':
clean = clean.replace('\/','\\')
else:
return clean.replace('AppData\\Roaming\\','AppData\\Local\\Packages\\XBMCFoundation.Kodi_4n2hpmxwrvr6p\\LocalCache\\Roaming\\')
if sys.platform == 'win32':
clean = clean.replace('\/','\\')
return clean
#----------------------------------------------------------------
# TUTORIAL #
def Text_File(path, mode, text = ''):
"""
Open/create a text file and read/write to it.
CODE: koding.Text_File(path, mode, [text])
AVAILABLE PARAMS:
(*) path - This is the path to the text file
(*) mode - This can be 'r' (for reading) or 'w' (for writing)
text - This is only required if you're writing to a file, this
is the text you want to enter. This will completely overwrite any
text already in the file.
EXAMPLE CODE:
HOME = koding.Physical_Path('special://home')
koding_test = os.path.join(HOME, 'koding_test.txt')
koding.Text_File(path=koding_test, mode='w', text='Well done, you\'ve created a text file containing this text!')
dialog.ok('CREATE TEXT FILE','If you check your home Kodi folder and you should now have a new koding_test.txt file in there.','[COLOR=gold]DO NOT DELETE IT YET![/COLOR]')
mytext = koding.Text_File(path=koding_test, mode='r')
dialog.ok('TEXT FILE CONTENTS','The text in the file created is:','[COLOR=dodgerblue]%s[/COLOR]'%mytext,'[COLOR=gold]CLICK OK TO DELETE THE FILE[/COLOR]')
try:
os.remove(koding_test)
except:
dialog.ok('FAILED TO REMOVE','Could not remove the file, looks like you might have it open in a text editor. Please manually remove yourself')
~"""
try:
textfile = xbmcvfs.File(path, mode)
if mode == 'r':
content = textfile.read()
textfile.close()
return content
if mode == 'w':
textfile.write(text)
textfile.close()
return True
except:
xbmc.log(Last_Error(),2)
return False
#----------------------------------------------------------------