Skip to content

Commit 35904d9

Browse files
Rasmus Oscar Welanderglpatcern
Rasmus Oscar Welander
authored andcommitted
Added file class, the resource class and an example
1 parent 4bec911 commit 35904d9

File tree

3 files changed

+691
-0
lines changed

3 files changed

+691
-0
lines changed

examples/file_api_example.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""
2+
example.py
3+
4+
Example script to demonstrate the usage of the CS3Client class.
5+
Start with an empty directory and you should end up with a directory structure like this:
6+
7+
test_directory1
8+
test_directory2
9+
test_directory3
10+
rename_file.txt (containing "Hello World")
11+
text_file.txt
12+
13+
14+
Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti.
15+
16+
Last updated: 29/07/2024
17+
"""
18+
19+
import logging
20+
import configparser
21+
from cs3client import CS3Client
22+
from cs3resource import Resource
23+
24+
config = configparser.ConfigParser()
25+
with open("default.conf") as fdef:
26+
config.read_file(fdef)
27+
# log
28+
log = logging.getLogger(__name__)
29+
30+
client = CS3Client(config, "cs3client", log)
31+
client.auth.set_client_secret("relativity")
32+
33+
# Authentication
34+
print(client.auth.get_token())
35+
36+
res = None
37+
38+
# mkdir
39+
for i in range(1, 4):
40+
directory_resource = Resource.from_file_ref_and_endpoint(f"/eos/user/r/rwelande/test_directory{i}")
41+
res = client.file.make_dir(directory_resource)
42+
if res is not None:
43+
print(res)
44+
45+
# touchfile
46+
touch_resource = Resource.from_file_ref_and_endpoint("/eos/user/r/rwelande/touch_file.txt")
47+
text_resource = Resource.from_file_ref_and_endpoint("/eos/user/r/rwelande/text_file.txt")
48+
res = client.file.touch_file(touch_resource)
49+
res = client.file.touch_file(text_resource)
50+
51+
if res is not None:
52+
print(res)
53+
54+
# setxattr
55+
resource = Resource.from_file_ref_and_endpoint("/eos/user/r/rwelande/text_file.txt")
56+
res = client.file.set_xattr(resource, "iop.wopi.lastwritetime", str(1720696124))
57+
58+
if res is not None:
59+
print(res)
60+
61+
# rmxattr
62+
res = client.file.remove_xattr(resource, "iop.wopi.lastwritetime")
63+
64+
if res is not None:
65+
print(res)
66+
67+
# stat
68+
res = client.file.stat(text_resource)
69+
70+
if res is not None:
71+
print(res)
72+
73+
# removefile
74+
res = client.file.remove_file(touch_resource)
75+
76+
if res is not None:
77+
print(res)
78+
79+
res = client.file.touch_file(touch_resource)
80+
81+
# rename
82+
rename_resource = Resource.from_file_ref_and_endpoint("/eos/user/r/rwelande/rename_file.txt")
83+
res = client.file.rename_file(resource, rename_resource)
84+
85+
if res is not None:
86+
print(res)
87+
88+
# writefile
89+
content = b"Hello World"
90+
size = len(content)
91+
res = client.file.write_file(rename_resource, content, size)
92+
93+
if res is not None:
94+
print(res)
95+
96+
# rmdir (same as deletefile)
97+
res = client.file.remove_file(directory_resource)
98+
99+
if res is not None:
100+
print(res)
101+
102+
# listdir
103+
list_directory_resource = Resource.from_file_ref_and_endpoint("/eos/user/r/rwelande")
104+
res = client.file.list_dir(list_directory_resource)
105+
106+
first_item = next(res, None)
107+
if first_item is not None:
108+
print(first_item)
109+
for item in res:
110+
print(item)
111+
else:
112+
print("empty response")
113+
114+
# readfile
115+
file_res = client.file.read_file(rename_resource)
116+
content = b""
117+
try:
118+
for chunk in file_res:
119+
if isinstance(chunk, Exception):
120+
raise chunk
121+
content += chunk
122+
print(content.decode("utf-8"))
123+
except Exception as e:
124+
print(f"An error occurred: {e}")
125+
print(e)

src/cs3resource.py

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
"""
2+
cs3resource.py
3+
4+
Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti.
5+
6+
Last updated: 29/07/2024
7+
"""
8+
9+
import cs3.storage.provider.v1beta1.resources_pb2 as cs3spr
10+
11+
12+
class Resource:
13+
"""Class to handle CS3 resources, the class can be initialized with an absolute path,
14+
a relative path or an opaque fileid as the "file" parameter (required).
15+
16+
Absolute path example: `/path/to/file`
17+
Relative path example: `<parent_opaque_id>/<base_filename>` or `<parent_opaque_id>/<path>/<to>/<file>`
18+
Opaque fileid example: `<opaque_file_id>`
19+
20+
The endpoint attribute contains the storage_id and space_id (always optional) separated by a `$` character,
21+
this is optional if the file is an absolute path.
22+
23+
endpoint example: `storage_id` or `storage_id$space_id`
24+
25+
"""
26+
27+
def __init__(
28+
self,
29+
abs_path: str | None = None,
30+
rel_path: str | None = None,
31+
opaque_id: str | None = None,
32+
parent_id: str | None = None,
33+
storage_id: str | None = None,
34+
space_id: str | None = None,
35+
) -> None:
36+
"""
37+
initializes the Resource class, either abs_path, rel_path or opaque_id is required
38+
and with rel_path the parent_id is also required, the rest parameters are fully optional.
39+
40+
41+
:param abs_path: absolute path (semi-optional)
42+
:param rel_path: relative path (semi-optional)
43+
:param parent_id: parent id (semi-optional)
44+
:param opaque_id: opaque id (semi-optional)
45+
:param storage_id: storage id (optional)
46+
:param space_id: space id (optional)
47+
"""
48+
self._abs_path: str | None = abs_path
49+
self._rel_path: str | None = rel_path
50+
self._parent_id: str | None = parent_id
51+
self._opaque_id: str | None = opaque_id
52+
self._space_id: str | None = space_id
53+
self._storage_id: str | None = storage_id
54+
55+
@classmethod
56+
def from_file_ref_and_endpoint(cls, file: str, endpoint: str | None = None) -> "Resource":
57+
"""
58+
Extracts the attributes from the file and endpoint and returns a resource.
59+
60+
:param file: The file reference
61+
:param endpoint: The storage id and space id (optional)
62+
:return: Resource object
63+
"""
64+
65+
abs_path = None
66+
rel_path = None
67+
opaque_id = None
68+
parent_id = None
69+
storage_id = None
70+
space_id = None
71+
72+
if file.startswith("/"):
73+
# assume we have an absolute path
74+
abs_path = file
75+
else:
76+
# try splitting endpoint
77+
parts = endpoint.split("$", 2)
78+
storage_id = parts[0]
79+
if len(parts) == 2:
80+
space_id = parts[1]
81+
if file.find("/") > 0:
82+
# assume we have an relative path,
83+
parent_id = file[: file.find("/")]
84+
rel_path = file[file.find("/") :]
85+
else:
86+
# assume we have an opaque fileid
87+
opaque_id = file
88+
return cls(abs_path, rel_path, opaque_id, parent_id, storage_id, space_id)
89+
90+
@property
91+
def ref(self) -> cs3spr.Reference:
92+
"""
93+
Generates a CS3 reference for a given resource, covering the following cases:
94+
absolute path, relative hybrid path, fully opaque fileid.
95+
96+
:return: The cs3 reference.
97+
May throw ValueError (Invalid Resource)
98+
"""
99+
if self._abs_path:
100+
return cs3spr.Reference(path=self._abs_path)
101+
if self._rel_path:
102+
return cs3spr.Reference(
103+
resource_id=cs3spr.ResourceId(
104+
storage_id=self._storage_id,
105+
space_id=self._space_id,
106+
opaque_id=self._parent_id,
107+
),
108+
path="." + self._rel_path,
109+
)
110+
if self._opaque_id:
111+
return cs3spr.Reference(
112+
resource_id=cs3spr.ResourceId(
113+
storage_id=self._storage_id,
114+
space_id=self._space_id,
115+
opaque_id=self._opaque_id,
116+
),
117+
path=".",
118+
)
119+
raise ValueError("Invalid Resource")
120+
121+
def recreate_endpoint_and_file(self) -> dict:
122+
"""
123+
Recreates the endpoint and file reference from the given resource
124+
125+
:return: (dict) {"file": fileref, "endpoint": endpoint}
126+
May throw ValueError (invalid resource)
127+
"""
128+
endpoint = self._storage_id
129+
if self._space_id:
130+
endpoint += f"${self._space_id}"
131+
if self._abs_path:
132+
return {"file": self._abs_path, "endpoint": endpoint}
133+
if self._parent_id and self._rel_path:
134+
return {"file": f"{self._parent_id}{self._rel_path}", "endpoint": endpoint}
135+
if self._opaque_id:
136+
return {"file": self._opaque_id, "endpoint": endpoint}
137+
raise ValueError("Invalid Resource")
138+
139+
@classmethod
140+
def from_cs3_ref(cls, reference: cs3spr.Reference) -> "Resource":
141+
"""
142+
Alternate constructor that reverses a CS3 reference to obtain a resource.ß
143+
144+
:param reference: The CS3 reference.
145+
:return: Resource object.
146+
May throw ValueError (Invalid reference)
147+
"""
148+
rel_path = None
149+
opaque_id = None
150+
parent_id = None
151+
storage_id = None
152+
space_id = None
153+
154+
if reference.path and reference.path.startswith("/"):
155+
# It's an absolute path, we can return straight away
156+
return Resource(abs_path=reference.path)
157+
elif reference.resource_id and reference.resource_id.storage_id:
158+
storage_id = reference.resource_id.storage_id
159+
if reference.resource_id.space_id:
160+
space_id = reference.resource_id.space_id
161+
if reference.path and len(reference.path) > 1:
162+
# It's a relative path (remove the "." in the relative path)
163+
rel_path = reference.path[1:]
164+
# The opaque_id is a parent id since it's a relative pathß
165+
parent_id = reference.resource_id.opaque_id
166+
else:
167+
opaque_id = reference.resource_id.opaque_id
168+
return Resource(
169+
abs_path=None,
170+
rel_path=rel_path,
171+
opaque_id=opaque_id,
172+
parent_id=parent_id,
173+
storage_id=storage_id,
174+
space_id=space_id,
175+
)
176+
raise ValueError("Invalid CS3 reference")
177+
178+
# It is possible that the same resource is different if abs_path is used in one
179+
# and the other is using opaque_id for example.
180+
def __eq__(self, other):
181+
"""redefine the equality operator to compare two resources"""
182+
if isinstance(other, Resource):
183+
return (
184+
self._abs_path == other._abs_path
185+
and self._rel_path == other._rel_path
186+
and self._parent_id == other._parent_id
187+
and self._opaque_id == other._opaque_id
188+
and self._space_id == other._space_id
189+
and self._storage_id == other._storage_id
190+
)
191+
return False
192+
193+
def get_file_ref_str(self):
194+
"""
195+
Generates a string from the file ref, '<type>="fileref">'
196+
197+
:return: str '<type>="fileref">'
198+
"""
199+
if self._abs_path:
200+
return f'absolute_path="{self._abs_path}"'
201+
elif self._rel_path:
202+
return f'relative_path="{self._parent_id}/{self._rel_path}"'
203+
elif self._opaque_id:
204+
return f'opaque_id="{self._opaque_id}"'

0 commit comments

Comments
 (0)