xbmcbackup/resources/lib/vfs.py
2019-08-27 14:55:22 -05:00

453 lines
13 KiB
Python

import xbmc
import xbmcvfs
import xbmcgui
import zipfile
import os.path
import sys
from . import dropbox
from . import utils as utils
from . dropbox.files import WriteMode,CommitInfo,UploadSessionCursor
from . authorizers import DropboxAuthorizer,GoogleDriveAuthorizer
class Vfs:
root_path = None
def __init__(self,rootString):
self.set_root(rootString)
def set_root(self,rootString):
old_root = self.root_path
self.root_path = rootString
#fix slashes
self.root_path = self.root_path.replace("\\","/")
#check if trailing slash is included
if(self.root_path[-1:] != "/"):
self.root_path = self.root_path + "/"
#return the old root
return old_root
def listdir(self,directory):
return {}
def mkdir(self,directory):
return True
def put(self,source,dest):
return True
def rmdir(self,directory):
return True
def rmfile(self,aFile):
return True
def exists(self,aFile):
return True
def rename(self,aFile,newName):
return True
def cleanup(self):
return True
class XBMCFileSystem(Vfs):
def listdir(self,directory):
return xbmcvfs.listdir(directory)
def mkdir(self,directory):
return xbmcvfs.mkdir(xbmc.translatePath(directory))
def put(self,source,dest):
return xbmcvfs.copy(xbmc.translatePath(source),xbmc.translatePath(dest))
def rmdir(self,directory):
return xbmcvfs.rmdir(directory,True)
def rmfile(self,aFile):
return xbmcvfs.delete(aFile)
def rename(self,aFile,newName):
return xbmcvfs.rename(aFile, newName)
def exists(self,aFile):
return xbmcvfs.exists(aFile)
class ZipFileSystem(Vfs):
zip = None
def __init__(self,rootString,mode):
self.root_path = ""
self.zip = zipfile.ZipFile(rootString,mode=mode,compression=zipfile.ZIP_DEFLATED,allowZip64=True)
def listdir(self,directory):
return [[],[]]
def mkdir(self,directory):
#self.zip.write(directory[len(self.root_path):])
return False
def put(self,source,dest):
aFile = xbmcvfs.File(xbmc.translatePath(source),'r')
self.zip.writestr(utils.encode(dest),aFile.read())
return True
def rmdir(self,directory):
return False
def exists(self,aFile):
return False
def cleanup(self):
self.zip.close()
def extract(self,aFile,path):
#extract zip file to path
self.zip.extract(aFile,path)
def listFiles(self):
return self.zip.infolist()
class DropboxFileSystem(Vfs):
MAX_CHUNK = 50 * 1000 * 1000 #dropbox uses 150, reduced to 50 for small mem systems
client = None
APP_KEY = ''
APP_SECRET = ''
def __init__(self,rootString):
self.set_root(rootString)
authorizer = DropboxAuthorizer()
if(authorizer.isAuthorized()):
self.client = authorizer.getClient()
else:
#tell the user to go back and run the authorizer
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30105))
sys.exit()
def listdir(self,directory):
directory = self._fix_slashes(directory)
if(self.client != None and self.exists(directory)):
files = []
dirs = []
metadata = self.client.files_list_folder(directory)
for aFile in metadata.entries:
if(isinstance(aFile,dropbox.files.FolderMetadata)):
dirs.append(utils.encode(aFile.name))
else:
files.append(utils.encode(aFile.name))
return [dirs,files]
else:
return [[],[]]
def mkdir(self,directory):
directory = self._fix_slashes(directory)
if(self.client != None):
#sort of odd but always return true, folder create is implicit with file upload
return True
else:
return False
def rmdir(self,directory):
directory = self._fix_slashes(directory)
if(self.client != None and self.exists(directory)):
#dropbox is stupid and will refuse to do this sometimes, need to delete recursively
dirs,files = self.listdir(directory)
for aDir in dirs:
self.rmdir(aDir)
#finally remove the root directory
self.client.files_delete(directory)
return True
else:
return False
def rmfile(self,aFile):
aFile = self._fix_slashes(aFile)
if(self.client != None and self.exists(aFile)):
self.client.files_delete(aFile)
return True
else:
return False
def exists(self,aFile):
aFile = self._fix_slashes(aFile)
if(self.client != None):
#can't list root metadata
if(aFile == ''):
return True
try:
meta_data = self.client.files_get_metadata(aFile)
#if we make it here the file does exist
return True
except:
return False
else:
return False
def put(self,source,dest,retry=True):
dest = self._fix_slashes(dest)
if(self.client != None):
#open the file and get its size
f = open(source,'rb')
f_size = os.path.getsize(source)
try:
if(f_size < self.MAX_CHUNK):
#use the regular upload
response = self.client.files_upload(f.read(),dest,mode=WriteMode('overwrite'))
else:
#start the upload session
upload_session = self.client.files_upload_session_start(f.read(self.MAX_CHUNK))
upload_cursor = UploadSessionCursor(upload_session.session_id,f.tell())
while(f.tell() < f_size):
#check if we should finish the upload
if((f_size - f.tell()) <= self.MAX_CHUNK):
#upload and close
self.client.files_upload_session_finish(f.read(self.MAX_CHUNK),upload_cursor,CommitInfo(dest,mode=WriteMode('overwrite')))
else:
#upload a part and store the offset
self.client.files_upload_session_append_v2(f.read(self.MAX_CHUNK),upload_cursor)
upload_cursor.offset = f.tell()
#if no errors we're good!
return True
except Exception as anError:
utils.log(str(anError))
#if we have an exception retry
if(retry):
return self.put(source,dest,False)
else:
#tried once already, just quit
return False
else:
return False
def get_file(self,source,dest):
if(self.client != None):
#write the file locally
f = self.client.files_download_to_file(dest,source)
return True
else:
return False
def _fix_slashes(self,filename):
result = filename.replace('\\','/')
#root needs to be a blank string
if(result == '/'):
result = ""
#if dir ends in slash, remove it
if(result[-1:] == "/"):
result = result[:-1]
return result
class GoogleDriveFilesystem(Vfs):
drive = None
history = {}
FOLDER_TYPE = 'application/vnd.google-apps.folder'
def __init__(self,rootString):
self.set_root(rootString)
authorizer = GoogleDriveAuthorizer()
if(authorizer.isAuthorized()):
self.drive = authorizer.getClient()
else:
#tell the user to go back and run the authorizer
xbmcgui.Dialog().ok(utils.getString(30010),utils.getString(30105))
sys.exit()
#make sure we have the folder we need
xbmc_folder = self._getGoogleFile(self.root_path)
if(xbmc_folder == None):
self.mkdir(self.root_path)
def listdir(self,directory):
files = []
dirs = []
if(not directory.startswith('/')):
directory = '/' + directory
#get the id of this folder
parentFolder = self._getGoogleFile(directory)
#need to do this after
if(not directory.endswith('/')):
directory = directory + '/'
if(parentFolder != None):
fileList = self.drive.ListFile({'q':"'" + parentFolder['id'] + "' in parents and trashed = false"}).GetList()
for aFile in fileList:
if(aFile['mimeType'] == self.FOLDER_TYPE):
dirs.append(utils.encode(aFile['title']))
else:
files.append(utils.encode(aFile['title']))
return [dirs,files]
def mkdir(self,directory):
result = True
if(not directory.startswith('/')):
directory = '/' + directory
if(directory.endswith('/')):
directory = directory[:-1]
#split the string by the directory separator
pathList = os.path.split(directory)
if(pathList[0] == '/'):
#we're at the root, just make the folder
newFolder = self.drive.CreateFile({'title': pathList[1], 'parent':'root','mimeType':self.FOLDER_TYPE})
newFolder.Upload()
else:
#get the id of the parent folder
parentFolder = self._getGoogleFile(pathList[0])
if(parentFolder != None):
newFolder = self.drive.CreateFile({'title': pathList[1],"parents":[{'kind':'drive#fileLink','id':parentFolder['id']}],'mimeType':self.FOLDER_TYPE})
newFolder.Upload()
else:
result = False
return result
def put(self,source,dest):
result = True
#make the name separate from the path
if(not dest.startswith('/')):
dest = '/' + dest
pathList = os.path.split(dest)
#get the parent location
parentFolder = self._getGoogleFile(pathList[0])
if(parentFolder != None):
#create a new file in this folder
newFile = self.drive.CreateFile({"title":pathList[1],"parents":[{'kind':'drive#fileLink','id':parentFolder['id']}]})
newFile.SetContentFile(source)
newFile.Upload()
else:
result = False
return result
def get_file(self,source, dest):
result = True
#get the id of this file
file = self._getGoogleFile(source)
if(file != None):
file.GetContentFile(dest)
else:
result = False
return result
def rmdir(self,directory):
result = True
#check that the folder exists
folder = self._getGoogleFile(directory)
if(folder != None):
#delete the folder
folder.Delete()
else:
result = False
return result
def rmfile(self,aFile):
#really just the same as the remove directory function
return self.rmdir(aFile)
def exists(self,aFile):
#attempt to get this file
foundFile = self._getGoogleFile(aFile)
if(foundFile != None):
return True
else:
return False
def rename(self,aFile,newName):
return True
def _getGoogleFile(self,file):
result = None
#file must start with / and not end with one (even directory)
if(not file.startswith('/')):
file = '/' + file
if(file.endswith('/')):
file = file[:-1]
if(self.history.has_key(file)):
result = self.history[file]
else:
pathList = os.path.split(file)
#end of recurision, we got the root
if(pathList[0] == '/'):
#get the id of this file (if it exists)
file_list = self.drive.ListFile({'q':"title='" + pathList[1] + "' and 'root' in parents and trashed=false"}).GetList()
if(len(file_list) > 0):
result = file_list[0]
self.history[pathList[1]] = result
else:
#recurse down the tree
current_file = pathList[1]
parentId = self._getGoogleFile(pathList[0])
if(parentId != None):
self.history[pathList[0]] = parentId
#attempt to get the id of this file, with this parent
file_list = file_list = self.drive.ListFile({'q':"title='" + current_file + "' and '" + parentId['id'] + "' in parents and trashed=false"}).GetList()
if(len(file_list) > 0):
result = file_list[0]
self.history[file] = result
return result