import zipfile import os.path import sys from kodi_six import xbmc, xbmcvfs, xbmcgui 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