Skip to content

Conversation

@nebukadhezer
Copy link

Changelog Description

Added Rclone as a provider. All is done via subprocess. so rclone needs to be present on PATH or the absolute path needs to be set.

Additional review information

need to double check the linter so leaving this as a draft until then

Testing notes:

Tested with Nextcloud as a rclone remote, via config file and just envvars, following the paradigm of some other providers to implement both. Tested on windows only.

@BigRoy BigRoy requested review from Copilot and kalisp January 29, 2026 19:02
@BigRoy BigRoy added type: enhancement Improvement of existing functionality or minor addition community Issues and PRs coming from the community members labels Jan 29, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds Rclone as a new provider for the SiteSync addon, enabling file synchronization through Rclone remotes. The implementation follows the existing provider pattern and supports both configuration file-based and environment variable-based authentication.

Changes:

  • Added RClone provider settings model with multiplatform path support and both config file and environment variable configuration options
  • Implemented RCloneHandler client with full file operations (upload, download, delete, list, create folder) via subprocess calls to rclone
  • Registered the new provider in the factory with a batch limit of 20

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
server/settings/settings.py Imports RClone settings model and registers "rclone" as a provider option
server/settings/providers/rclone.py Defines RCloneSubmodel with configuration fields for rclone executable, config path, remote settings, and authentication
client/ayon_sitesync/providers/rclone.py Implements RCloneHandler class with all required provider methods using rclone subprocess calls
client/ayon_sitesync/providers/lib.py Registers RCloneHandler in the provider factory
client/ayon_sitesync/providers/resources/rclone.png Adds Rclone provider icon

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 208 to 209
cmd = [self.rclone_path, "--config",
self._config_path]
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a config path is provided, the code constructs a command including "--config" with an empty string (line 208-209). This could cause issues if self._config_path is an empty string instead of being truly absent. The condition at line 221 checks 'if not self._config_path', which would be True for empty strings, but by then the command has already been built incorrectly. Consider restructuring the logic to handle config path construction only when it's non-empty, or ensure empty strings are treated consistently.

Copilot uses AI. Check for mistakes.
Comment on lines 37 to 43
description="Egg webdav, Use this if you do not have a rclone.conf")

url: str = Field("", title="Remote Url",
description="Use this if you do not have a rclone.conf")

vendor: str = Field("", title="Vendor",
description="Egg nextcloud, check rclone docs for the right name, Use this if you do not have a rclone.conf")
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description contains a typo. "Egg" should be "e.g." (the Latin abbreviation for "exempli gratia" meaning "for example").

Suggested change
description="Egg webdav, Use this if you do not have a rclone.conf")
url: str = Field("", title="Remote Url",
description="Use this if you do not have a rclone.conf")
vendor: str = Field("", title="Vendor",
description="Egg nextcloud, check rclone docs for the right name, Use this if you do not have a rclone.conf")
description="e.g. webdav, Use this if you do not have a rclone.conf")
url: str = Field("", title="Remote Url",
description="Use this if you do not have a rclone.conf")
vendor: str = Field("", title="Vendor",
description="e.g. nextcloud, check rclone docs for the right name, Use this if you do not have a rclone.conf")

Copilot uses AI. Check for mistakes.
env[env_url] = self.url
env_user = f"RCLONE_CONFIG_{self.remote_name.upper()}_USER"
env[env_user] = self.user
env["RCLONE_CONFIG"] = "NUL"
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using "NUL" as the RCLONE_CONFIG value is Windows-specific. On Unix-based systems (Linux/macOS), the null device is "/dev/null". This should use os.devnull which provides a cross-platform null device path, or use conditional logic based on the platform to ensure compatibility across Windows, Linux, and macOS environments.

Suggested change
env["RCLONE_CONFIG"] = "NUL"
env["RCLONE_CONFIG"] = os.devnull

Copilot uses AI. Check for mistakes.
return subprocess.check_output(cmd).decode().strip()

@staticmethod
def _parse_rclone_json(output: dict):
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type hint for the 'output' parameter is incorrect. The parameter is typed as 'dict' but the function actually handles string and bytes types (as shown by the isinstance checks on lines 303-304). The type hint should be 'str | bytes' or 'Union[str, bytes]' to accurately reflect the actual parameter types.

Suggested change
def _parse_rclone_json(output: dict):
def _parse_rclone_json(output: str | bytes):

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,66 @@
from pydantic import Field
from ayon_server.settings import BaseSettingsModel, secrets_enum
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import secrets_enum is not used anywhere in this file. This should be removed to avoid unused imports.

Suggested change
from ayon_server.settings import BaseSettingsModel, secrets_enum
from ayon_server.settings import BaseSettingsModel

Copilot uses AI. Check for mistakes.
Comment on lines 36 to 46
type: str = Field("", title="Remote Type",
description="Egg webdav, Use this if you do not have a rclone.conf")

url: str = Field("", title="Remote Url",
description="Use this if you do not have a rclone.conf")

vendor: str = Field("", title="Vendor",
description="Egg nextcloud, check rclone docs for the right name, Use this if you do not have a rclone.conf")

user: str = Field("", title="User",
description="Use this if you do not have a rclone.conf")
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fields 'type', 'url', 'vendor', and 'user' are missing the 'scope' parameter. Looking at other fields in this model and similar settings in other providers (e.g., sftp.py lines 37-41), these fields should have scope defined. Consider adding scope=["studio", "project", "site"] to maintain consistency with other provider-specific configuration fields.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I would make them consistent... but I am not sure which ones to take as a baseline... just studio project or all three

self.rclone_path = self.presets.get("rclone_executable_path", {}).get(
platform.system().lower(), "rclone")

self._root = self.presets.get("root", "").strip("/")
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _root instance variable is set at line 19 but never used anywhere in the class. The get_roots_config method uses self.presets.get('root', '/') instead. Consider removing this unused variable or using it in get_roots_config if it was intended to be used.

Suggested change
self._root = self.presets.get("root", "").strip("/")

Copilot uses AI. Check for mistakes.
Comment on lines 286 to 290
def _parse_time(self, rclone_time_str):
"""Parse rclone's ISO8601 time string to timestamp."""
dt = datetime.fromisoformat(rclone_time_str.replace("Z", "+00:00"))
return dt.timestamp()

Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _parse_time method is defined but never used anywhere in the class. If this method is not needed for future functionality, it should be removed to avoid maintaining unused code. If it's intended for future use, consider adding a comment explaining its purpose.

Suggested change
def _parse_time(self, rclone_time_str):
"""Parse rclone's ISO8601 time string to timestamp."""
dt = datetime.fromisoformat(rclone_time_str.replace("Z", "+00:00"))
return dt.timestamp()

Copilot uses AI. Check for mistakes.
nebukadhezer and others added 6 commits February 2, 2026 20:48
…hints, better handle parsed rclone json output, reduce exceptions from non existing folders
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@nebukadhezer
Copy link
Author

@kalisp I am not sure about the scope settings... especially with the PW not being obscured and then everyone can see it on the site settings so I set them to project and studio only... would convert the PR now away from draft.

@nebukadhezer nebukadhezer marked this pull request as ready for review February 3, 2026 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Issues and PRs coming from the community members type: enhancement Improvement of existing functionality or minor addition

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants