diff --git a/cterasdk/asynchronous/core/files/browser.py b/cterasdk/asynchronous/core/files/browser.py index 3b8a7ff4..d848f8d2 100644 --- a/cterasdk/asynchronous/core/files/browser.py +++ b/cterasdk/asynchronous/core/files/browser.py @@ -1,6 +1,6 @@ from .. import query from ....cio.core.commands import Open, Upload, Download, EnsureDirectory, \ - UnShare, CreateDirectory, GetMetadata, GetProperties, ListVersions, RecursiveIterator, \ + UnShare, CreateDirectory, GetMetadata, GetProperties, ListVersions, RecursiveIterator, SearchIterator, \ Delete, Recover, Rename, GetShareMetadata, Link, Copy, Move, ResourceIterator, GetPermalink, GetExternalShareInfo from ....cio.core.types import InvitationPath from ....lib.storage import commonfs @@ -58,7 +58,7 @@ async def listdir(self, path=None, include_deleted=False): :raises cterasdk.exceptions.io.core.ListDirectoryError: Raised on error fetching directory contents. """ async with EnsureDirectory(io.listdir, self._core, path): - async for o in ResourceIterator(query.iterator, self._core, path, None, include_deleted, None, None).a_execute(): + async for o in ResourceIterator(query.iterator, self._core, path, None, include_deleted, None, None, None).a_execute(): yield o async def properties(self, path): @@ -151,6 +151,17 @@ async def permalink(self, path): """ return await GetPermalink(io.listdir, self._core, path).a_execute() + async def search(self, path, filters): + """ + Search for files and folders + + :param str path: Path + :param list[cterasdk.core.types.SearchFilter], optional filters: Search filters + """ + async with EnsureDirectory(io.listdir, self._core, path): + async for o in SearchIterator(query.iterator, self._core, path, filters).a_execute(): + yield o + class CloudDrive(FileBrowser): """Async CloudDrive API with upload and sharing functionality.""" diff --git a/cterasdk/cio/core/commands.py b/cterasdk/cio/core/commands.py index 3e3ac5fb..c5544eb8 100644 --- a/cterasdk/cio/core/commands.py +++ b/cterasdk/cio/core/commands.py @@ -437,12 +437,13 @@ async def _a_execute(self): class ListDirectory(PortalCommand): """List""" - def __init__(self, function, receiver, path, depth, include_deleted, search_criteria, limit): + def __init__(self, function, receiver, path, depth, include_deleted, search_criteria, search_filters, limit): super().__init__(function, receiver) self.path = automatic_resolution(path, receiver.context) self.depth = depth self.include_deleted = include_deleted self.search_criteria = search_criteria + self.search_filters = search_filters self.limit = limit def get_parameter(self): @@ -452,6 +453,8 @@ def get_parameter(self): builder.include_deleted() if self.search_criteria: builder.searchCriteria(self.search_criteria) + if self.search_filters: + builder.searchFilters(self.search_filters) if self.limit: builder.limit(self.limit) return builder.build() @@ -499,10 +502,16 @@ def _fetch_resources_error(self, e): raise error from e +class SearchIterator(ResourceIterator): + + def __init__(self, function, receiver, path, search_filters): + super().__init__(function, receiver, path, 'Infinity', False, None, search_filters, None) + + class GetMetadata(ListDirectory): def __init__(self, function, receiver, path, suppress_error=False): - super().__init__(function, receiver, path, 0, False, None, None) + super().__init__(function, receiver, path, 0, False, None, None, None) self.suppress_error = suppress_error def _before_command(self): @@ -570,7 +579,7 @@ def _generator(self): def generate(self): for path in self._generator(): try: - for o in ResourceIterator(self._function, self._receiver, path, None, self.include_deleted, None, None).execute(): + for o in ResourceIterator(self._function, self._receiver, path, None, self.include_deleted, None, None, None).execute(): yield self._process_object(o) except (exceptions.io.core.ListDirectoryError, exceptions.io.core.PrivilegeError) as e: RecursiveIterator._suppress_error(e) @@ -578,7 +587,8 @@ def generate(self): async def a_generate(self): for path in self._generator(): try: - async for o in ResourceIterator(self._function, self._receiver, path, None, self.include_deleted, None, None).a_execute(): + async for o in ResourceIterator(self._function, self._receiver, + path, None, self.include_deleted, None, None, None).a_execute(): yield self._process_object(o) except (exceptions.io.core.ListDirectoryError, exceptions.io.core.PrivilegeError) as e: RecursiveIterator._suppress_error(e) diff --git a/cterasdk/cio/core/types.py b/cterasdk/cio/core/types.py index 4cb6da1a..10c56849 100644 --- a/cterasdk/cio/core/types.py +++ b/cterasdk/cio/core/types.py @@ -292,6 +292,10 @@ def searchCriteria(self, criteria): self.param.searchCriteria = criteria # pylint: disable=attribute-defined-outside-init return self + def searchFilters(self, filters): + self.param.searchFilters = filters # pylint: disable=attribute-defined-outside-init + return self + def include_deleted(self): self.param.includeDeleted = True # pylint: disable=attribute-defined-outside-init return self diff --git a/cterasdk/core/files/browser.py b/cterasdk/core/files/browser.py index f84bae98..a304d443 100644 --- a/cterasdk/core/files/browser.py +++ b/cterasdk/core/files/browser.py @@ -1,6 +1,6 @@ from .. import query from ...cio.core.commands import Open, Upload, Download, EnsureDirectory, \ - UnShare, CreateDirectory, GetMetadata, GetProperties, ListVersions, RecursiveIterator, \ + UnShare, CreateDirectory, GetMetadata, GetProperties, ListVersions, RecursiveIterator, SearchIterator, \ Delete, Recover, Rename, GetShareMetadata, Link, Copy, Move, ResourceIterator, GetPermalink, GetExternalShareInfo from ...cio.core.types import InvitationPath from ...lib.storage import commonfs @@ -45,12 +45,13 @@ def download(self, path, objects=None, destination=None): return Download(io.handle_many if properties.is_dir else io.handle, self._core, properties.path, properties, objects, destination).execute() - def listdir(self, path=None, include_deleted=False): + def listdir(self, path=None, include_deleted=False, filters=None): """ List directory contents. :param str, optional path: Path. Defaults to the Cloud Drive root. :param bool, optional include_deleted: Include deleted files. Defaults to False. + :param list[cterasdk.core.types.SearchFilter], optional filters: Search filters :returns: Directory contents. :rtype: list[cterasdk.cio.core.types.PortalResource] :raises cterasdk.exceptions.io.core.GetMetadataError: If the directory was not found. @@ -58,7 +59,7 @@ def listdir(self, path=None, include_deleted=False): :raises cterasdk.exceptions.io.core.ListDirectoryError: Raised on error fetching directory contents. """ with EnsureDirectory(io.listdir, self._core, path): - return ResourceIterator(query.iterator, self._core, path, None, include_deleted, None, None).execute() + return ResourceIterator(query.iterator, self._core, path, None, include_deleted, None, filters, None).execute() def properties(self, path): """ @@ -149,6 +150,16 @@ def permalink(self, path): """ return GetPermalink(io.listdir, self._core, path).execute() + def search(self, path, filters): + """ + Search for files and folders + + :param str path: Path + :param list[cterasdk.core.types.SearchFilter], optional filters: Search filters + """ + with EnsureDirectory(io.listdir, self._core, path): + return SearchIterator(query.iterator, self._core, path, filters).execute() + class CloudDrive(FileBrowser): """CloudDrive extends FileBrowser with upload and share functionality.""" diff --git a/cterasdk/core/types.py b/cterasdk/core/types.py index 41f40ab1..4f7385dc 100644 --- a/cterasdk/core/types.py +++ b/cterasdk/core/types.py @@ -1212,3 +1212,61 @@ def to_server_object(self): param.collaborator.type = CollaboratorType.from_account(self.account) param.screened_file_types = [extension.lstrip('.') for extension in self.extensions] return param + + +class SearchOperator: + EQUALS = 'eq' + NOT_EQUALS = 'neq' + CONTAINS = 'contains' + + +class SearchFilter(Object): + + def __init__(self, field, operator, value): + super().__init__() + self._classname = 'SearchFilter' + self.field = field + self.operator = operator + self.value = value + + +class FilenameFilter(SearchFilter): + + def __init__(self, operator, value): + super().__init__('name', operator, value) + + @staticmethod + def eq(value): + return FilenameFilter(SearchOperator.EQUALS, value) + + @staticmethod + def ne(value): + return FilenameFilter(SearchOperator.NOT_EQUALS, value) + + @staticmethod + def contains(value): + return FilenameFilter(SearchOperator.CONTAINS, value) + + +class TypeFilter(SearchFilter): + + def __init__(self, operator, value): + super().__init__('extension', operator, value) + + @staticmethod + def eq(value): + return TypeFilter(SearchOperator.EQUALS, value) + + @staticmethod + def ne(value): + return TypeFilter(SearchOperator.NOT_EQUALS, value) + + +class ContentFilter(SearchFilter): + + def __init__(self, operator, value): + super().__init__('content', operator, value) + + @staticmethod + def contains(value): + return ContentFilter(SearchOperator.CONTAINS, value) diff --git a/docs/source/UserGuides/Portal/Files.rst b/docs/source/UserGuides/Portal/Files.rst index 5ab62b13..1cfaa27c 100644 --- a/docs/source/UserGuides/Portal/Files.rst +++ b/docs/source/UserGuides/Portal/Files.rst @@ -170,6 +170,7 @@ Retrieve a Permalink permalink = files.permalink('My Files/Keystone Project.docx') print(f'Permalink: {permalink}') + Create a Public Link -------------------- @@ -187,6 +188,23 @@ Create a Public Link public_url_rw = files.public_link('My Files', access='RW', expire_in=30) print(f'Public read-write link: {public_url_rw}') + +Search +------ + +.. automethod:: cterasdk.core.files.browser.FileBrowser.search + :noindex: + +.. code-block:: python + + filters = [ + core_types.ContentFilter.contains('The quick brown fox jumped over the lazy dog'), + ] + + for i in files.public_link('My Files', filters): + print(i.path) + + File Handles ------------ @@ -521,6 +539,9 @@ Asynchronous API .. automethod:: cterasdk.asynchronous.core.files.browser.FileBrowser.permalink :noindex: +.. automethod:: cterasdk.asynchronous.core.files.browser.FileBrowser.search + :noindex: + .. automethod:: cterasdk.asynchronous.core.files.browser.CloudDrive.upload :noindex: