Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cterasdk/asynchronous/core/files/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ async def versions(core, path):


async def walk(core, scope, path, include_deleted=False):
target = fs.CorePath.instance(scope, path)
await ensure_directory(core, target)
paths = [fs.CorePath.instance(scope, path)]
while len(paths) > 0:
path = paths.pop(0)
Expand All @@ -53,7 +55,7 @@ async def walk(core, scope, path, include_deleted=False):
async def mkdir(core, path):
with fs.makedir(path) as param:
response = await core.v1.api.execute('', 'makeCollection', param)
fs.accept_response(response)
fs.accept_error(response)


async def makedirs(core, path):
Expand Down Expand Up @@ -173,7 +175,7 @@ async def wrapper(core):
"""
uid, filename, directory = await _validate_destination(core, name, destination)
with fs.upload(core, filename, directory, size, fd) as param:
return await core.io.upload(str(uid), param)
return fs.validate_transfer_success(await core.io.upload(str(uid), param), destination.join(name).reference.as_posix())
return wrapper


Expand Down
36 changes: 30 additions & 6 deletions cterasdk/cio/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
from contextlib import contextmanager
from ..objects.uri import quote, unquote
from ..common import Object, DateTimeUtils
from ..core.enum import ProtectionLevel, CollaboratorType, SearchType, PortalAccountType, FileAccessMode, FileAccessError
from ..core.enum import ProtectionLevel, CollaboratorType, SearchType, PortalAccountType, FileAccessMode, FileAccessError, \
UploadError
from ..core.types import PortalAccount, UserAccount, GroupAccount
from ..exceptions.io import ResourceExistsError, PathValidationError, NameSyntaxError, ReservedNameError, RestrictedRoot
from ..exceptions.io import ResourceExistsError, PathValidationError, NameSyntaxError, \
ReservedNameError, RestrictedRoot, InsufficientPermission
from ..exceptions.io import UploadException, OutOfQuota, RejectedByPolicy, NoStorageBucket, WindowsACLError
from ..lib.iterator import DefaultResponse
from . import common

Expand Down Expand Up @@ -181,7 +184,7 @@ def build(self):
class FetchResourcesResponse(DefaultResponse):

def __init__(self, response):
accept_response(response.errorType)
accept_error(response.errorType)
super().__init__(response)

@property
Expand Down Expand Up @@ -291,7 +294,7 @@ def handle(path):


def destination_prerequisite_conditions(destination, name):
if not destination.reference.root:
if not len(destination.reference.parts) > 0:
raise RestrictedRoot()
if any(c in name for c in ['\\', '/', ':', '?', '&', '<', '>', '"', '|']):
raise NameSyntaxError()
Expand All @@ -311,6 +314,26 @@ def upload(core, name, destination, size, fd):
yield param


def validate_transfer_success(response, path):
if response.rc:
logger.error('Upload of file: "%s" failed.', path)
if response.msg == UploadError.UserQuotaViolation:
raise OutOfQuota('User', path)
if response.msg == UploadError.PortalQuotaViolation:
raise OutOfQuota('Team Portal', path)
if response.msg == UploadError.FolderQuotaViolation:
raise OutOfQuota('Cloud drive folder', path)
if response.msg == UploadError.RejectedByPolicy:
raise RejectedByPolicy(path)
if response.msg == UploadError.WindowsACL:
raise WindowsACLError(path)
if response.msg.startswith(UploadError.NoStorageBucket):
raise NoStorageBucket(path)
raise UploadException(f'Upload failed. Reason: {response.msg}', path)
if not response.rc and response.msg == 'OK':
logger.info('Upload successful. Saved to: %s', path)


@contextmanager
def handle_many(directory, objects):
param = Object()
Expand Down Expand Up @@ -508,15 +531,16 @@ def obtain_current_accounts(param):
return current_accounts


def accept_response(error_type):
def accept_error(error_type):
"""
Check if response contains an error.
"""
error = {
FileAccessError.FileWithTheSameNameExist: ResourceExistsError(),
FileAccessError.DestinationNotExists: PathValidationError(),
FileAccessError.InvalidName: NameSyntaxError(),
FileAccessError.ReservedName: ReservedNameError()
FileAccessError.ReservedName: ReservedNameError(),
FileAccessError.PermissionDenied: InsufficientPermission()
}.get(error_type, None)
try:
if error:
Expand Down
4 changes: 2 additions & 2 deletions cterasdk/cio/edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def makedir(path):
yield path.absolute
except CTERAException as error:
try:
accept_response(error.response.message.msg, directory)
accept_error(error.response.message.msg, directory)
except ResourceExistsError:
logger.info('Directory already exists: %s', directory)
logger.info('Directory created: %s', directory)
Expand Down Expand Up @@ -126,7 +126,7 @@ def upload(name, destination, fd):
yield param


def accept_response(response, reference):
def accept_error(response, reference):
error = {
"File exists": ResourceExistsError(),
"Creating a folder in this location is forbidden": RestrictedPathError(),
Expand Down
6 changes: 4 additions & 2 deletions cterasdk/clients/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ async def download_zip(self, path, data, **kwargs):
class AsyncUpload(AsyncClient):

async def upload(self, path, data, **kwargs):
return await super().form_data(path, data, **kwargs)
response = await super().form_data(path, data, **kwargs)
return await response.xml()


class AsyncWebDAV(AsyncClient):
Expand Down Expand Up @@ -253,7 +254,8 @@ def download_zip(self, path, data, **kwargs):
class Upload(Client):

def upload(self, path, data, **kwargs):
return super().form_data(path, data, **kwargs)
response = super().form_data(path, data, **kwargs)
return response.xml()


class WebDAV(Client):
Expand Down
1 change: 1 addition & 0 deletions cterasdk/clients/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def authenticate_then_execute(self, *args, **kwargs):
except SessionExpired:
logger.error('Session expired.')
self.cookies.clear()
raise
logger.error('Not logged in.')
raise NotLoggedIn()
return authenticate_then_execute
15 changes: 15 additions & 0 deletions cterasdk/core/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,21 @@ class Reports:
FolderGroups = 'folderGroupsStatisticsReport'


class UploadError:
"""
Upload Error

:ivar QuotaViolation: User is out of quota.
:ivar RejectedByPolicy: Rejected by Cloud Drive policy rule.
"""
FolderQuotaViolation = 'Folder is out of quota'
UserQuotaViolation = 'User is out of quota'
PortalQuotaViolation = 'Portal is out of quota'
RejectedByPolicy = "Rejected by Cloud Drive policy rule"
NoStorageBucket = "No available storage location"
WindowsACL = "Illegal access to NTACL folder"


class FileAccessError:
"""
File Access Error
Expand Down
9 changes: 5 additions & 4 deletions cterasdk/core/files/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ def versions(core, path):


def walk(core, scope, path, include_deleted=False):
ensure_directory(core, path)
paths = [fs.CorePath.instance(scope, path)]
target = fs.CorePath.instance(scope, path)
ensure_directory(core, target)
paths = [target]
while len(paths) > 0:
path = paths.pop(0)
entries = listdir(core, path, include_deleted=include_deleted)
Expand All @@ -55,7 +56,7 @@ def walk(core, scope, path, include_deleted=False):
def mkdir(core, path):
with fs.makedir(path) as param:
response = core.api.execute('', 'makeCollection', param)
fs.accept_response(response)
fs.accept_error(response)


def makedirs(core, path):
Expand Down Expand Up @@ -175,7 +176,7 @@ def wrapper(core):
"""
uid, filename, directory = _validate_destination(core, name, destination)
with fs.upload(core, filename, directory, size, fd) as param:
return core.io.upload(str(uid), param)
return fs.validate_transfer_success(core.io.upload(str(uid), param), destination.join(name).reference.as_posix())
return wrapper


Expand Down
40 changes: 40 additions & 0 deletions cterasdk/exceptions/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,43 @@ class RestrictedRoot(RemoteStorageException):

def __init__(self):
super().__init__('Storing files to the root directory is forbidden.', '/')


class InsufficientPermission(RemoteStorageException):

def __init__(self):
super().__init__('Permission denied: You must have appropriate permissions to access this resource.')


class UploadException(RemoteStorageException):
"""
Upload Exception

:ivar str path: Path
"""
def __init__(self, message, path):
super().__init__(f'Upload failed: {message}.', path)


class OutOfQuota(UploadException):

def __init__(self, entity, path):
super().__init__(f'{entity} is out of quota', path)


class RejectedByPolicy(UploadException):

def __init__(self, path):
super().__init__('Rejected by Cloud Drive policy rule', path)


class NoStorageBucket(UploadException):

def __init__(self, path):
super().__init__('No available storage location', path)


class WindowsACLError(UploadException):

def __init__(self, path):
super().__init__('Unable to store file in a Windows ACL-enabled cloud folder', path)
4 changes: 2 additions & 2 deletions docs/source/UserGuides/DataServices/DirectIO.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ During testing, you may need to disable TLS verification if the Portal or Object
Blocks API
==========

.. automethod:: cterasdk.direct.client.Client.blocks
.. automethod:: cterasdk.direct.client.DirectIO.blocks
:noindex:


Expand Down Expand Up @@ -122,7 +122,7 @@ Blocks API
Streamer API
============

.. automethod:: cterasdk.direct.client.Client.streamer
.. automethod:: cterasdk.direct.client.DirectIO.streamer
:noindex:

.. code-block:: python
Expand Down