mirror of
https://github.com/robweber/xbmcbackup.git
synced 2026-04-02 16:47:59 +02:00
added pydrive files
This commit is contained in:
322
resources/lib/pydrive/files.py
Normal file
322
resources/lib/pydrive/files.py
Normal file
@@ -0,0 +1,322 @@
|
||||
import io
|
||||
import mimetypes
|
||||
|
||||
from apiclient import errors
|
||||
from apiclient.http import MediaIoBaseUpload
|
||||
from functools import wraps
|
||||
|
||||
from .apiattr import ApiAttribute
|
||||
from .apiattr import ApiAttributeMixin
|
||||
from .apiattr import ApiResource
|
||||
from .apiattr import ApiResourceList
|
||||
from .auth import LoadAuth
|
||||
|
||||
|
||||
class FileNotUploadedError(RuntimeError):
|
||||
"""Error trying to access metadata of file that is not uploaded."""
|
||||
|
||||
|
||||
class ApiRequestError(IOError):
|
||||
"""Error while making any API requests."""
|
||||
|
||||
|
||||
class FileNotDownloadableError(RuntimeError):
|
||||
"""Error trying to download file that is not downloadable."""
|
||||
|
||||
|
||||
def LoadMetadata(decoratee):
|
||||
"""Decorator to check if the file has metadata and fetches it if not.
|
||||
|
||||
:raises: ApiRequestError, FileNotUploadedError
|
||||
"""
|
||||
@wraps(decoratee)
|
||||
def _decorated(self, *args, **kwargs):
|
||||
if not self.uploaded:
|
||||
self.FetchMetadata()
|
||||
return decoratee(self, *args, **kwargs)
|
||||
return _decorated
|
||||
|
||||
|
||||
class GoogleDriveFileList(ApiResourceList):
|
||||
"""Google Drive FileList instance.
|
||||
|
||||
Equivalent to Files.list() in Drive APIs.
|
||||
"""
|
||||
|
||||
def __init__(self, auth=None, param=None):
|
||||
"""Create an instance of GoogleDriveFileList."""
|
||||
super(GoogleDriveFileList, self).__init__(auth=auth, metadata=param)
|
||||
|
||||
@LoadAuth
|
||||
def _GetList(self):
|
||||
"""Overwritten method which actually makes API call to list files.
|
||||
|
||||
:returns: list -- list of pydrive.files.GoogleDriveFile.
|
||||
"""
|
||||
self.metadata = self.auth.service.files().list(**dict(self)).execute()
|
||||
result = []
|
||||
for file_metadata in self.metadata['items']:
|
||||
tmp_file = GoogleDriveFile(
|
||||
auth=self.auth,
|
||||
metadata=file_metadata,
|
||||
uploaded=True)
|
||||
result.append(tmp_file)
|
||||
return result
|
||||
|
||||
|
||||
class GoogleDriveFile(ApiAttributeMixin, ApiResource):
|
||||
"""Google Drive File instance.
|
||||
|
||||
Inherits ApiResource which inherits dict.
|
||||
Can access and modify metadata like dictionary.
|
||||
"""
|
||||
content = ApiAttribute('content')
|
||||
uploaded = ApiAttribute('uploaded')
|
||||
metadata = ApiAttribute('metadata')
|
||||
|
||||
def __init__(self, auth=None, metadata=None, uploaded=False):
|
||||
"""Create an instance of GoogleDriveFile.
|
||||
|
||||
:param auth: authorized GoogleAuth instance.
|
||||
:type auth: pydrive.auth.GoogleAuth
|
||||
:param metadata: file resource to initialize GoogleDirveFile with.
|
||||
:type metadata: dict.
|
||||
:param uploaded: True if this file is confirmed to be uploaded.
|
||||
:type uploaded: bool.
|
||||
"""
|
||||
ApiAttributeMixin.__init__(self)
|
||||
ApiResource.__init__(self)
|
||||
self.metadata = {}
|
||||
self.dirty = {'content': False}
|
||||
self.auth = auth
|
||||
self.uploaded = uploaded
|
||||
if uploaded:
|
||||
self.UpdateMetadata(metadata)
|
||||
elif metadata:
|
||||
self.update(metadata)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Overwrites manner of accessing Files resource.
|
||||
|
||||
If this file instance is not uploaded and id is specified,
|
||||
it will try to look for metadata with Files.get().
|
||||
|
||||
:param key: key of dictionary query.
|
||||
:type key: str.
|
||||
:returns: value of Files resource
|
||||
:raises: KeyError, FileNotUploadedError
|
||||
"""
|
||||
try:
|
||||
return dict.__getitem__(self, key)
|
||||
except KeyError, e:
|
||||
if self.uploaded:
|
||||
raise KeyError(e)
|
||||
if self.get('id'):
|
||||
self.FetchMetadata()
|
||||
return dict.__getitem__(self, key)
|
||||
else:
|
||||
raise FileNotUploadedError()
|
||||
|
||||
def SetContentString(self, content):
|
||||
"""Set content of this file to be a string.
|
||||
|
||||
Creates io.BytesIO instance of utf-8 encoded string.
|
||||
Sets mimeType to be 'text/plain' if not specified.
|
||||
|
||||
:param content: content of the file in string.
|
||||
:type content: str.
|
||||
"""
|
||||
self.content = io.BytesIO(content.encode('utf-8'))
|
||||
if self.get('mimeType') is None:
|
||||
self['mimeType'] = 'text/plain'
|
||||
|
||||
def SetContentFile(self, filename):
|
||||
"""Set content of this file from a file.
|
||||
|
||||
Opens the file specified by this method.
|
||||
Will be read, uploaded, and closed by Upload() method.
|
||||
Sets metadata 'title' and 'mimeType' automatically if not specified.
|
||||
|
||||
:param filename: name of the file to be uploaded.
|
||||
:type filename: str.
|
||||
"""
|
||||
self.content = open(filename, 'rb')
|
||||
|
||||
if self.get('title') is None:
|
||||
self['title'] = filename
|
||||
if self.get('mimeType') is None:
|
||||
self['mimeType'] = mimetypes.guess_type(filename)[0]
|
||||
|
||||
def GetContentString(self):
|
||||
"""Get content of this file as a string.
|
||||
|
||||
:returns: str -- utf-8 decoded content of the file
|
||||
:raises: ApiRequestError, FileNotUploadedError, FileNotDownloadableError
|
||||
"""
|
||||
if self.content is None or type(self.content) is not io.BytesIO:
|
||||
self.FetchContent()
|
||||
return self.content.getvalue().decode('utf-8')
|
||||
|
||||
def GetContentFile(self, filename, mimetype=None):
|
||||
"""Save content of this file as a local file.
|
||||
|
||||
:param filename: name of the file to write to.
|
||||
:type filename: str.
|
||||
:raises: ApiRequestError, FileNotUploadedError, FileNotDownloadableError
|
||||
"""
|
||||
if self.content is None or type(self.content) is not io.BytesIO:
|
||||
self.FetchContent(mimetype)
|
||||
f = open(filename, 'wb')
|
||||
f.write(self.content.getvalue())
|
||||
f.close()
|
||||
|
||||
@LoadAuth
|
||||
def FetchMetadata(self):
|
||||
"""Download file's metadata from id using Files.get().
|
||||
|
||||
:raises: ApiRequestError, FileNotUploadedError
|
||||
"""
|
||||
file_id = self.metadata.get('id') or self.get('id')
|
||||
if file_id:
|
||||
try:
|
||||
metadata = self.auth.service.files().get(fileId=file_id).execute()
|
||||
except errors.HttpError, error:
|
||||
raise ApiRequestError(error)
|
||||
else:
|
||||
self.uploaded = True
|
||||
self.UpdateMetadata(metadata)
|
||||
else:
|
||||
raise FileNotUploadedError()
|
||||
|
||||
@LoadMetadata
|
||||
def FetchContent(self, mimetype=None):
|
||||
"""Download file's content from download_url.
|
||||
|
||||
:raises: ApiRequestError, FileNotUploadedError, FileNotDownloadableError
|
||||
"""
|
||||
download_url = self.metadata.get('downloadUrl')
|
||||
if download_url:
|
||||
self.content = io.BytesIO(self._DownloadFromUrl(download_url))
|
||||
self.dirty['content'] = False
|
||||
return
|
||||
|
||||
export_links = self.metadata.get('exportLinks')
|
||||
if export_links and export_links.get(mimetype):
|
||||
self.content = io.BytesIO(
|
||||
self._DownloadFromUrl(export_links.get(mimetype)))
|
||||
self.dirty['content'] = False
|
||||
return
|
||||
|
||||
raise FileNotDownloadableError(
|
||||
'No downloadLink/exportLinks for mimetype found in metadata')
|
||||
|
||||
def Upload(self, param=None):
|
||||
"""Upload/update file by choosing the most efficient method.
|
||||
|
||||
:param param: additional parameter to upload file.
|
||||
:type param: dict.
|
||||
:raises: ApiRequestError
|
||||
"""
|
||||
if self.uploaded or self.get('id') is not None:
|
||||
if self.dirty['content']:
|
||||
self._FilesUpdate(param=param)
|
||||
else:
|
||||
self._FilesPatch(param=param)
|
||||
else:
|
||||
self._FilesInsert(param=param)
|
||||
|
||||
def Delete(self):
|
||||
if self.get('id') is not None:
|
||||
self.auth.service.files().delete(fileId=self.get('id')).execute()
|
||||
|
||||
@LoadAuth
|
||||
def _FilesInsert(self, param=None):
|
||||
"""Upload a new file using Files.insert().
|
||||
|
||||
:param param: additional parameter to upload file.
|
||||
:type param: dict.
|
||||
:raises: ApiRequestError
|
||||
"""
|
||||
if param is None:
|
||||
param = {}
|
||||
param['body'] = self.GetChanges()
|
||||
try:
|
||||
if self.dirty['content']:
|
||||
param['media_body'] = self._BuildMediaBody()
|
||||
metadata = self.auth.service.files().insert(**param).execute()
|
||||
except errors.HttpError, error:
|
||||
raise ApiRequestError(error)
|
||||
else:
|
||||
self.uploaded = True
|
||||
self.dirty['content'] = False
|
||||
self.UpdateMetadata(metadata)
|
||||
|
||||
@LoadAuth
|
||||
@LoadMetadata
|
||||
def _FilesUpdate(self, param=None):
|
||||
"""Update metadata and/or content using Files.Update().
|
||||
|
||||
:param param: additional parameter to upload file.
|
||||
:type param: dict.
|
||||
:raises: ApiRequestError, FileNotUploadedError
|
||||
"""
|
||||
if param is None:
|
||||
param = {}
|
||||
param['body'] = self.GetChanges()
|
||||
param['fileId'] = self.metadata.get('id')
|
||||
try:
|
||||
if self.dirty['content']:
|
||||
param['media_body'] = self._BuildMediaBody()
|
||||
metadata = self.auth.service.files().update(**param).execute()
|
||||
except errors.HttpError, error:
|
||||
raise ApiRequestError(error)
|
||||
else:
|
||||
self.uploaded = True
|
||||
self.dirty['content'] = False
|
||||
self.UpdateMetadata(metadata)
|
||||
|
||||
@LoadAuth
|
||||
@LoadMetadata
|
||||
def _FilesPatch(self, param=None):
|
||||
"""Update metadata using Files.Patch().
|
||||
|
||||
:param param: additional parameter to upload file.
|
||||
:type param: dict.
|
||||
:raises: ApiRequestError, FileNotUploadedError
|
||||
"""
|
||||
if param is None:
|
||||
param = {}
|
||||
param['body'] = self.GetChanges()
|
||||
param['fileId'] = self.metadata.get('id')
|
||||
try:
|
||||
metadata = self.auth.service.files().patch(**param).execute()
|
||||
except errors.HttpError, error:
|
||||
raise ApiRequestError(error)
|
||||
else:
|
||||
self.UpdateMetadata(metadata)
|
||||
|
||||
def _BuildMediaBody(self):
|
||||
"""Build MediaIoBaseUpload to get prepared to upload content of the file.
|
||||
|
||||
Sets mimeType as 'application/octet-stream' if not specified.
|
||||
|
||||
:returns: MediaIoBaseUpload -- instance that will be used to upload content.
|
||||
"""
|
||||
if self.get('mimeType') is None:
|
||||
self['mimeType'] = 'application/octet-stream'
|
||||
|
||||
return MediaIoBaseUpload(self.content, self['mimeType'])
|
||||
|
||||
@LoadAuth
|
||||
def _DownloadFromUrl(self, url):
|
||||
"""Download file from url using provided credential.
|
||||
|
||||
:param url: link of the file to download.
|
||||
:type url: str.
|
||||
:returns: str -- content of downloaded file in string.
|
||||
:raises: ApiRequestError
|
||||
"""
|
||||
resp, content = self.auth.service._http.request(url)
|
||||
if resp.status != 200:
|
||||
raise ApiRequestError('Cannot download file: %s' % resp)
|
||||
return content
|
||||
Reference in New Issue
Block a user