-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add polybox storage #527
Changes from 8 commits
578240d
1a97357
ebb0042
6fc7c96
24a2592
1bdf0ce
1405f06
74edcc3
dc7ce49
f6cef33
addb28b
9a58d06
176a359
888891b
8744a6b
6c463aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
import json | ||
import tempfile | ||
from collections.abc import Generator | ||
from copy import deepcopy | ||
from pathlib import Path | ||
from typing import TYPE_CHECKING, Any, NamedTuple, Union, cast | ||
|
||
|
@@ -156,6 +157,91 @@ def __patch_schema_remove_oauth_propeties(spec: list[dict[str, Any]]) -> None: | |
options.append(option) | ||
storage["Options"] = options | ||
|
||
@staticmethod | ||
def __find_webdav_storage(spec: list[dict[str, Any]]) -> dict[str, Any]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: this is too specific to me, I think having |
||
"""Find and return the WebDAV storage schema from the spec.""" | ||
webdav_storage = next((s for s in spec if s["Prefix"] == "webdav"), None) | ||
if not webdav_storage: | ||
raise errors.ValidationError(message="WebDAV storage not found in schema.") | ||
return deepcopy(webdav_storage) | ||
|
||
@staticmethod | ||
def __add_webdav_based_storage( | ||
spec: list[dict[str, Any]], | ||
prefix: str, | ||
name: str, | ||
description: str, | ||
url_value: str, | ||
public_link_help: str, | ||
) -> None: | ||
"""Create a modified copy of WebDAV storage and add it to the schema.""" | ||
# Find WebDAV storage schema and create a modified copy | ||
storage_copy = RCloneValidator.__find_webdav_storage(spec) | ||
storage_copy.update({"Prefix": prefix, "Name": name, "Description": description}) | ||
|
||
custom_options = [ | ||
{ | ||
"Name": "access", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: I'm a bit confused by this, this is a field called access, but lower down its values are used as provider. In rclone schemas, the providers should be in the field There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I see where the confusion came from. I was trying to make it flexible by adding an option for access (personal and shared), but it ended up being a bit unclear. I’ll change it so the modes are set in the provider options instead, and I’ll use the provider property to specify when an option applies to a specific mode (personal or shared). |
||
"Help": "Choose the mode to access the data source.", | ||
"Provider": "", | ||
"Default": "", | ||
"Value": None, | ||
"Examples": [ | ||
{"Value": "personal", "Help": "Use Private to connect a folder that only you use", "Provider": ""}, | ||
{ | ||
"Value": "shared", | ||
"Help": "To connect a folder you share with others, both personal & shared folders.", | ||
"Provider": "", | ||
}, | ||
], | ||
"Required": False, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: this field is mandatory, no? Especially since it doesn't have a default set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, it is |
||
"Type": "string", | ||
"ShortOpt": "", | ||
"Hide": 0, | ||
"IsPassword": False, | ||
"NoPrefix": False, | ||
"Advanced": False, | ||
"Exclusive": False, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, thanks for clarify that |
||
"Sensitive": False, | ||
"DefaultStr": "", | ||
"ValueStr": "", | ||
}, | ||
{ | ||
"Name": "public_link", | ||
"Help": public_link_help, | ||
"Provider": "shared", | ||
"Default": "", | ||
"Value": None, | ||
"Examples": None, | ||
"ShortOpt": "", | ||
"Hide": 0, | ||
"Required": True, | ||
"IsPassword": False, | ||
"NoPrefix": False, | ||
"Advanced": False, | ||
"Exclusive": False, | ||
"Sensitive": False, | ||
"DefaultStr": "", | ||
"ValueStr": "", | ||
"Type": "string", | ||
}, | ||
] | ||
storage_copy["Options"].extend(custom_options) | ||
|
||
# use provider to indicate if the option is for an personal o shared storage | ||
for option in storage_copy["Options"]: | ||
if option["Name"] == "url": | ||
option.update({"Provider": "personal", "Default": url_value, "Required": False}) | ||
elif option["Name"] in ["bearer_token", "bearer_token_command", "headers", "user"]: | ||
option["Provider"] = "personal" | ||
|
||
# Remove obsolete options no longer applicable for Polybox or SwitchDrive | ||
storage_copy["Options"] = [ | ||
o for o in storage_copy["Options"] if o["Name"] not in ["vendor", "nextcloud_chunk_size"] | ||
] | ||
|
||
spec.append(storage_copy) | ||
|
||
def apply_patches(self, spec: list[dict[str, Any]]) -> None: | ||
"""Apply patches to RClone schema.""" | ||
patches = [ | ||
|
@@ -167,6 +253,25 @@ def apply_patches(self, spec: list[dict[str, Any]]) -> None: | |
for patch in patches: | ||
patch(spec) | ||
|
||
# Apply patches for PolyBox and SwitchDrive to the schema. | ||
self.__add_webdav_based_storage( | ||
spec, | ||
prefix="polybox", | ||
name="PolyBox", | ||
description="Polybox", | ||
url_value="https://polybox.ethz.ch/remote.php/webdav/", | ||
public_link_help="Shared folder link. E.g., https://polybox.ethz.ch/index.php/s/8NffJ3rFyHaVyyy", | ||
) | ||
|
||
self.__add_webdav_based_storage( | ||
spec, | ||
prefix="switchDrive", | ||
name="SwitchDrive", | ||
description="SwitchDrive", | ||
url_value="https://drive.switch.ch/remote.php/webdav/", | ||
public_link_help="Shared folder link. E.g., https://drive.switch.ch/index.php/s/OPSd72zrs5JG666", | ||
) | ||
|
||
def validate(self, configuration: Union["RCloneConfig", dict[str, Any]], keep_sensitive: bool = False) -> None: | ||
"""Validates an RClone config.""" | ||
provider = self.get_provider(configuration) | ||
|
@@ -182,10 +287,12 @@ async def test_connection( | |
except errors.ValidationError as e: | ||
return ConnectionResult(False, str(e)) | ||
|
||
# Obscure configuration and transform if needed | ||
obscured_config = await self.obscure_config(configuration) | ||
transformed_config = self.transform_polybox_switchdriver_config(obscured_config) | ||
|
||
with tempfile.NamedTemporaryFile(mode="w+", delete=False, encoding="utf-8") as f: | ||
config = "\n".join(f"{k}={v}" for k, v in obscured_config.items()) | ||
config = "\n".join(f"{k}={v}" for k, v in transformed_config.items()) | ||
f.write(f"[temp]\n{config}") | ||
f.close() | ||
proc = await asyncio.create_subprocess_exec( | ||
|
@@ -245,6 +352,45 @@ def get_private_fields( | |
provider = self.get_provider(configuration) | ||
return provider.get_private_fields(configuration) | ||
|
||
@staticmethod | ||
def transform_polybox_switchdriver_config( | ||
configuration: Union["RCloneConfig", dict[str, Any]], | ||
) -> Union["RCloneConfig", dict[str, Any]]: | ||
"""Transform the configuration for public access.""" | ||
storage_type = configuration.get("type") | ||
|
||
# Only process Polybox or SwitchDrive configurations | ||
if storage_type not in {"polybox", "switchDrive"}: | ||
return configuration | ||
|
||
configuration["type"] = "webdav" | ||
|
||
provider = configuration.get("provider") | ||
|
||
if provider == "personal": | ||
configuration["url"] = configuration.get("url") or ( | ||
"https://polybox.ethz.ch/remote.php/webdav/" | ||
if storage_type == "polybox" | ||
else "https://drive.switch.ch/remote.php/webdav/" | ||
) | ||
return configuration | ||
|
||
## Set url and username when is a shared configuration | ||
configuration["url"] = ( | ||
"https://polybox.ethz.ch/public.php/webdav/" | ||
if storage_type == "polybox" | ||
else "https://drive.switch.ch/public.php/webdav/" | ||
) | ||
public_link = configuration.get("public_link") | ||
|
||
if not public_link: | ||
raise ValueError("Missing 'public_link' for public access configuration.") | ||
|
||
# Extract the user from the public link | ||
configuration["user"] = public_link.split("/")[-1] | ||
|
||
return configuration | ||
|
||
|
||
class RCloneTriState(BaseModel): | ||
"""Represents a Tristate of true|false|unset.""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: these should be
elif
since they're mutually exclusive.