Skip to content

Commit f22f18a

Browse files
authored
Saimon/ut file browser (#292)
1 parent dfb8406 commit f22f18a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1967
-1119
lines changed

cterasdk/asynchronous/core/base_command.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ class BaseCommand:
33

44
def __init__(self, core):
55
self._core = core
6+
7+
def session(self):
8+
return self._core.session()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .browser import CloudDrive # noqa: E402, F401
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
from ....cio.core import CorePath
2+
from ....lib import FileSystem
3+
from ..base_command import BaseCommand
4+
from . import io
5+
6+
7+
class FileBrowser(BaseCommand):
8+
9+
def __init__(self, core):
10+
super().__init__(core)
11+
self._scope = f'/{self._core.context}/webdav'
12+
self._filesystem = FileSystem.instance()
13+
14+
async def handle(self, path):
15+
"""
16+
Get File Handle.
17+
18+
:param str path: Path to a file
19+
"""
20+
handle_function = await io.handle(self.normalize(path))
21+
return await handle_function(self._core)
22+
23+
async def handle_many(self, directory, *objects):
24+
"""
25+
Get a Zip Archive File Handle.
26+
27+
:param str directory: Path to a folder
28+
:param args objects: List of files and folders
29+
"""
30+
handle_many_function = await io.handle_many(self.normalize(directory), *objects)
31+
return await handle_many_function(self._core)
32+
33+
async def listdir(self, path, depth=None, include_deleted=False):
34+
"""
35+
List Directory
36+
37+
:param str path: Path
38+
:param bool,optional include_deleted: Include deleted files, defaults to False
39+
"""
40+
return await io.listdir(self._core, self.normalize(path), depth=depth, include_deleted=include_deleted)
41+
42+
async def versions(self, path):
43+
"""
44+
List snapshots of a file or directory
45+
46+
:param str path: Path
47+
"""
48+
return await io.versions(self._core, self.normalize(path))
49+
50+
async def walk(self, path, include_deleted=False):
51+
"""
52+
Walk Directory Contents
53+
54+
:param str path: Path to walk
55+
:param bool,optional include_deleted: Include deleted files, defaults to False
56+
"""
57+
return io.walk(self._core, self._scope, path, include_deleted=include_deleted)
58+
59+
async def public_link(self, path, access='RO', expire_in=30):
60+
"""
61+
Create a public link to a file or a folder
62+
63+
:param str path: The path of the file to create a link to
64+
:param str,optional access: Access policy of the link, defaults to 'RO'
65+
:param int,optional expire_in: Number of days until the link expires, defaults to 30
66+
"""
67+
return await io.public_link(self._core, self.normalize(path), access, expire_in)
68+
69+
async def copy(self, *paths, destination=None):
70+
"""
71+
Copy one or more files or folders
72+
73+
:param list[str] paths: List of paths
74+
:param str destination: Destination
75+
"""
76+
if destination is None:
77+
raise ValueError('Copy destination was not specified.')
78+
return await io.copy(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination))
79+
80+
async def permalink(self, path):
81+
"""
82+
Get Permalink for Path.
83+
84+
:param str path: Path.
85+
"""
86+
p = self.normalize(path)
87+
contents = [e async for e in await io.listdir(self._core,
88+
p.parent, 1, False, p.name, 1)] # pylint: disable=unnecessary-comprehension
89+
if contents and contents[0].name == p.name:
90+
return contents[0].permalink
91+
raise FileNotFoundError('File not found.', path)
92+
93+
def normalize(self, entries):
94+
return CorePath.instance(self._scope, entries)
95+
96+
97+
class CloudDrive(FileBrowser):
98+
99+
async def upload(self, name, size, destination, handle):
100+
"""
101+
Upload from file handle.
102+
103+
:param str name: File name.
104+
:param str size: File size.
105+
:param str destination: Path to remote directory.
106+
:param object handle: Handle.
107+
"""
108+
upload_function = await io.upload(name, size, self.normalize(destination), handle)
109+
return await upload_function(self._core)
110+
111+
async def upload_file(self, path, destination):
112+
"""
113+
Upload a file.
114+
115+
:param str path: Local path
116+
:param str destination: Remote path
117+
"""
118+
with open(path, 'rb') as handle:
119+
metadata = self._filesystem.properties(path)
120+
response = await self.upload(metadata['name'], metadata['size'], destination, handle)
121+
return response
122+
123+
async def mkdir(self, path):
124+
"""
125+
Create a new directory
126+
127+
:param str path: Directory path
128+
"""
129+
return await io.mkdir(self._core, self.normalize(path))
130+
131+
async def makedirs(self, path):
132+
"""
133+
Create a directory recursively
134+
135+
:param str path: Directory path
136+
"""
137+
return await io.makedirs(self._core, self.normalize(path))
138+
139+
async def rename(self, path, name):
140+
"""
141+
Rename a file
142+
143+
:param str path: Path of the file or directory to rename
144+
:param str name: The name to rename to
145+
"""
146+
return await io.rename(self._core, self.normalize(path), name)
147+
148+
async def delete(self, *paths):
149+
"""
150+
Delete one or more files or folders
151+
152+
:param str path: Path
153+
"""
154+
return await io.remove(self._core, *[self.normalize(path) for path in paths])
155+
156+
async def undelete(self, *paths):
157+
"""
158+
Recover one or more files or folders
159+
160+
:param str path: Path
161+
"""
162+
return await io.recover(self._core, *[self.normalize(path) for path in paths])
163+
164+
async def move(self, *paths, destination=None):
165+
"""
166+
Move one or more files or folders
167+
168+
:param list[str] paths: List of paths
169+
:param str destination: Destination
170+
"""
171+
if destination is None:
172+
raise ValueError('Move destination was not specified.')
173+
return await io.move(self._core, *[self.normalize(path) for path in paths], destination=self.normalize(destination))
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import logging
2+
from ....cio.common import encode_request_parameter
3+
from ....cio import core as fs
4+
from ....cio import exceptions
5+
from .. import query
6+
from ....lib import FetchResourcesResponse
7+
8+
9+
logger = logging.getLogger('cterasdk.core')
10+
11+
12+
async def listdir(core, path, depth=None, include_deleted=False, search_criteria=None, limit=None):
13+
with fs.fetch_resources(path, depth, include_deleted, search_criteria, limit) as param:
14+
if param.depth > 0:
15+
return query.iterator(core, '', param, 'fetchResources', callback_response=FetchResourcesResponse)
16+
return await core.v1.api.execute('', 'fetchResources', param)
17+
18+
19+
async def root(core, path):
20+
response = await listdir(core, path, 0)
21+
if response.root is None:
22+
raise exceptions.RemoteStorageException(path.absolute)
23+
return response.root
24+
25+
26+
async def versions(core, path):
27+
with fs.versions(path):
28+
return await core.v1.api.execute('', 'listSnapshots', path.absolute)
29+
30+
31+
async def walk(core, scope, path, include_deleted=False):
32+
paths = [fs.CorePath.instance(scope, path)]
33+
while len(paths) > 0:
34+
path = paths.pop(0)
35+
entries = await listdir(core, path, include_deleted=include_deleted)
36+
async for e in entries:
37+
if e.isFolder:
38+
paths.append(fs.CorePath.instance(scope, e))
39+
yield e
40+
41+
42+
async def mkdir(core, path):
43+
with fs.makedir(path) as param:
44+
response = await core.v1.api.execute('', 'makeCollection', param)
45+
fs.accept_response(response, path.reference.as_posix())
46+
47+
48+
async def makedirs(core, path):
49+
directories = path.parts
50+
for i in range(1, len(directories) + 1):
51+
path = fs.CorePath.instance(path.scope, '/'.join(directories[:i]))
52+
try:
53+
await mkdir(core, path)
54+
except exceptions.ResourceExistsError:
55+
logger.debug('Resource already exists: %s', path.reference.as_posix())
56+
57+
58+
async def rename(core, path, name):
59+
with fs.rename(path, name) as param:
60+
return await core.v1.api.execute('', 'moveResources', param)
61+
62+
63+
async def remove(core, *paths):
64+
with fs.delete(*paths) as param:
65+
return await core.v1.api.execute('', 'deleteResources', param)
66+
67+
68+
async def recover(core, *paths):
69+
with fs.recover(*paths) as param:
70+
return await core.v1.api.execute('', 'restoreResources', param)
71+
72+
73+
async def copy(core, *paths, destination=None):
74+
with fs.copy(*paths, destination=destination) as param:
75+
return await core.v1.api.execute('', 'copyResources', param)
76+
77+
78+
async def move(core, *paths, destination=None):
79+
with fs.move(*paths, destination=destination) as param:
80+
return await core.v1.api.execute('', 'moveResources', param)
81+
82+
83+
async def retrieve_remote_dir(core, directory):
84+
resource = await root(core, directory)
85+
if not resource.isFolder:
86+
raise exceptions.RemoteStorageException('The destination path is not a directory', None, path=directory.absolute)
87+
return str(resource.cloudFolderInfo.uid)
88+
89+
90+
async def handle(path):
91+
"""
92+
Create function to retrieve file handle.
93+
94+
:param cterasdk.cio.edge.CorePath path: Path to file.
95+
:returns: Callable function to retrieve file handle.
96+
:rtype: callable
97+
"""
98+
async def wrapper(core):
99+
"""
100+
Get file handle.
101+
102+
:param cterasdk.objects.synchronous.core.Portal core: Portal object.
103+
"""
104+
with fs.handle(path) as param:
105+
return await core.io.download(param)
106+
return wrapper
107+
108+
109+
async def handle_many(directory, *objects):
110+
"""
111+
Create function to retrieve zip archive
112+
113+
:param cterasdk.cio.edge.CorePath directory: Path to directory.
114+
:param args objects: List of files and folders.
115+
:returns: Callable function to retrieve file handle.
116+
:rtype: callable
117+
"""
118+
async def wrapper(core):
119+
"""
120+
Upload file from metadata and file handle.
121+
122+
:param cterasdk.objects.synchronous.core.Portal core: Portal object.
123+
:param str name: File name.
124+
:param object handle: File handle.
125+
"""
126+
with fs.handle_many(directory, objects) as param:
127+
return await core.io.download_zip(await retrieve_remote_dir(core, directory), encode_request_parameter(param))
128+
return wrapper
129+
130+
131+
async def upload(name, size, destination, fd):
132+
"""
133+
Create upload function
134+
135+
:param str name: File name.
136+
:param cterasdk.cio.core.CorePath destination: Path to directory.
137+
:param object fd: File handle.
138+
:returns: Callable function to start the upload.
139+
:rtype: callable
140+
"""
141+
async def wrapper(core):
142+
"""
143+
Upload file from metadata and file handle.
144+
145+
:param cterasdk.objects.synchronous.core.Portal core: POrtal object.
146+
"""
147+
target = await retrieve_remote_dir(core, destination)
148+
with fs.upload(core, name, destination, size, fd) as param:
149+
return await core.io.upload(target, param)
150+
return wrapper
151+
152+
153+
async def public_link(core, path, access, expire_in):
154+
with fs.public_link(path, access, expire_in) as param:
155+
response = await core.v1.api.execute('', 'createShare', param)
156+
return response.publicLink

cterasdk/asynchronous/core/login.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
from ...exceptions import CTERAException
55

66

7+
logger = logging.getLogger('cterasdk.core')
8+
9+
710
class Login(BaseCommand):
811
"""
912
CTERA Portal Login APIs
@@ -19,14 +22,23 @@ async def login(self, username, password):
1922
host = self._core.host()
2023
try:
2124
await self._core.v1.api.form_data('/login', {'j_username': username, 'j_password': password})
22-
logging.getLogger('cterasdk.core').info("User logged in. %s", {'host': host, 'user': username})
25+
logger.info("User logged in. %s", {'host': host, 'user': username})
2326
except CTERAException:
24-
logging.getLogger('cterasdk.core').error("Login failed. %s", {'host': host, 'user': username})
27+
logger.error("Login failed. %s", {'host': host, 'user': username})
2528
raise
2629

30+
async def sso(self, ctera_ticket):
31+
"""
32+
Login using a Portal ticket.
33+
34+
:param str ticket: SSO Ticket.
35+
"""
36+
logger.info('Logging in using a Portal ticket.')
37+
await self._core.v1.ctera.form_data('/sso', {'ctera_ticket': ctera_ticket})
38+
2739
async def logout(self):
2840
"""
2941
Log out of the portal
3042
"""
3143
await self._core.v1.api.form_data('/logout', {})
32-
logging.getLogger('cterasdk.core').info("User logged out. %s", {'host': self._core.host()})
44+
logger.info("User logged out. %s", {'host': self._core.host()})

cterasdk/asynchronous/core/notifications.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ async def get(self, cloudfolders=None, cursor=None, max_results=None):
3232
param.max_results = max_results if max_results is not None else 2000
3333
logging.getLogger('cterasdk.metadata.connector').debug('Listing updates.')
3434
response = await self._core.v2.api.post('/metadata/list', param)
35-
print(response)
3635
if response is not None:
3736
return CursorResponse(response)
3837
logging.getLogger('cterasdk.metadata.connector').error('An error occurred while trying to retrieve notifications.')

0 commit comments

Comments
 (0)