NextCloud addon

Hello!
Maybe it’s a noob question but still i want to figure it out)
I started developing my small NextCloud addon whitch will generate share links in publishing integrate stage to further use in task manager (kitsu, ftrack, shotgrid). I’ve done a working logic but and trying reverse engineer looking at already build addons but cant figure out some things where du i need to put things.

this is code with task to solve for now:

import requests
from requests.auth import HTTPBasicAuth
import xml.etree.ElementTree as ET


nextcloudurl = "https://cloud.sample.com" #this knob need to be exposed in plugin inteface in studio settings
ocs_api_url = "/ocs/v2.php/apps/files_sharing/api/v1/shares"
req_url = nextcloudurl + ocs_api_url
nextcloud_login = 'login' #this knob need to be exposed in plugin inteface in studio settings and use ayon secrets
nextcloud_password = 'password' #this knob need to be exposed in plugin inteface in studio settings and use ayon secrets

nextcloud_mount='/Work' #this knob need to be exposed in plugin inteface in studio settings
ayon_mount='Z:' #this knob need to be exposed in plugin inteface in studio settings
path = 'Z:/ayon_test/test/sequences/sq01/sh040/publish/plate/plateVideo_1/v001' #this knob need to be exposed in plugin inteface and use ayon tockens for filepath
path = path.replace(ayon_mount,nextcloud_mount)

mygetparams = {'path' : path}
mypostparams = {'path' : path,
                'shareType' : 3,
                }
myheaders = {"OCS-APIRequest" : "true", "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"}

try:
    responce = requests.get(req_url,
                            auth=HTTPBasicAuth(nextcloud_login, nextcloud_password),
                            headers=myheaders,
                            params=mygetparams)
    try:
        url = ET.fromstring(responce.text).find('data/element/url').text #this string var needs to be stored as a tocken (like {verstion}) to further use in kitstu note or ftrtack or shortgrid to be inside comment secsion of a published file
        print('url exists there it is')
        print(url)
    except:
        print('url dont exist creating new one')
        responce = requests.post(req_url,
                            auth=HTTPBasicAuth(nextcloud_login, nextcloud_password),
                            headers=myheaders,
                            params=mypostparams)
        url = ET.fromstring(responce.text).find('data/url').text #this string var needs to be stored as a tocken (like {verstion}) to further use in kitstu note or ftrtack or shortgrid to be inside comment secsion of a published file
        print(url)
except:
    print('not working setting')

I’m duing disassembly into parts some addons like kitsu to understand how they works but I’m relearning coding because last time i code in univercity so its difficult and documentation doesnt make a lot off things clear beacause when i look at example I can see a ton of classes whitch is not codumented anywhere

Your code will be easier to dissect if you create dedicated function with description comments of what it should do (called docstrings).

But admittedly - if you have very limited coding knowledge you might have a bit of a struggle here.

You’ll need to implement:

  • A studio addon (there is an ayon-addon-example repository)
  • Likely create a publish plug-in if you want this to always occur during publish
  • Or add a loader plug-in to expose the functionality to the loader. (Delivery action may be a relevant example)
  • Package the addon and install it (or test it in dev mode)

Here’s an example on how to tweak your current code (not saying this would be functional):

import requests
from requests.auth import HTTPBasicAuth
import xml.etree.ElementTree as ET

# These should be configured using Studio Settings and login+user use AYON secrets
NEXTCLOUD_URL = "https://cloud.sample.com"
NEXTCLOUD_LOGIN = 'login'
NEXTCLOUD_PASSWORD = 'password'


def create_nextcloud_link(path: str) -> str:
    """Create a nextcloud link to a filepath or folder path

    Arguments:
        path (str): The filepath of directory to share.
            If it's already shared, it will return the
            existing share url.

    Returns:
        str: The nextcloud URL
    """

    req_url = f"{NEXTCLOUD_URL}/ocs/v2.php/apps/files_sharing/api/v1/shares"
    auth = HTTPBasicAuth(NEXTCLOUD_LOGIN, NEXTCLOUD_PASSWORD)
    headers = {
        "OCS-APIRequest": "true",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
    }

    # Get existing share URL if it exists
    get_params = {
        'path': path
    }
    response = requests.get(req_url,
                            auth=auth,
                            headers=headers,
                            params=get_params)
    # TODO: Check response status
    if response:
        return ET.fromstring(response.text).find('data/element/url').text

    # Create a new share URL
    post_params = {
        'path': path,
        'shareType': 3,
    }
    response = requests.get(req_url,
                            auth=auth,
                            headers=headers,
                            params=post_params)
    # TODO: Check response status
    if response:
        return ET.fromstring(response.text).find('data/url').text

    # TODO: Raise error if it failed to create or get share url?


# Test usage:
nextcloud_mount = '/Work'
ayon_mount = 'Z:'
path = 'Z:/ayon_test/test/sequences/sq01/sh040/publish/plate/plateVideo_1/v001'
path = path.replace(ayon_mount, nextcloud_mount)
url = create_nextcloud_link(path)
print(url)

I see what you mean thanks) but i dont get how to add my knobs to ayon settings tab and how to store url as a tocken

Those would have to be exposed on an addon. The ayon-example-addon contains some example settings code.

2 Likes

@BigRoy
Hey) i used kitsu addon, example addond and some other to reference the structure and logic and manage to create something

Now i have interface with working ( i believe) secrets but i struggle understand why i dont see addon on publish stage and dont understand how to debug it because terminalls dont show me anything related to my addon

I’ll continue digging into examples but maybe you will filnd some time to have a look
i’ve learned how to work with git so there you can see a link

2 Likes

Really nice work, @timsergeeff

I’d say try this:

  • Make sure your studio addon is enabled in your active production bundle. That’s what your launcher launches with and starts the applications with. Without that, you may only see the settings in your server front-end. However, looking at your screenshot it seems that is done. :white_check_mark:
  • Inside the launched applications, open the publisher. Then check the “Details” tab.
    1. Check the “Crashed Plugins” top right. Is your plug-in listed there? If so, you can expand it to get to the relevant error.
    2. If no errors, check the list of plug-ins on the left side. Is your plug-in listed? If not, then somehow your plug-in isn’t registered or not being picked up.
    3. If it is listed, then do a publish. At the end look at the Details tab again. Is your plug-in “green” or “white” (you may need to disable “hide skipped plugins”). If it remained white then it didn’t run at all due to the publishing system thinking it wasn’t relevant to whatever you were publishing (like not matching publish families, etc. however I didn’t quickly see any selective filtering in your plugin).
    4. If it was green, your plugin processed fine, without errors. Which would mean that what you’d expect it to it actually didn’t do - so you plugin.process() method likely is not doing what you want.

It’s good to note that if you do a debug log from the plug-in, like self.log.debug("Hello world!") that the debug logs do not show on the Report page, but they are accessible on the Details tab. The report page only shows info logs or higher (like warning, error, etc.)


Some other things to fix:

  • You may want to offset your plug-ins order to ensure it runs after the base Integrator because it’s set to the same order and may run before it. So set e.g. order = pyblish.api.IntegratorOrder + 0.1. It may very well be this is the issue you are facing.
  • Your plug-in is “optional” however, the publishing system pyblish doesn’t support that per instance so we have custom logic inside AYON to implement that. It requires inheriting from OptionalPyblishPluginMixin like here and then returning early in the process function like here.
2 Likes

Awesome! @timsergeeff

Let my share some thoughts.

  1. Repo Name: Would you like to follow our naming convention e.g. ayon-nextcloud instead of nextcloud_ayon_dev ?
  2. I think creating a dev branch could be better than using dev in the repo’s name.
  3. Your addon in this line expects your publish folder to be inside plugins folder.
  4. your plugin IntegrateNextcloudShare needs two things.
    a. inherit OptionalPyblishPluginMixin as BigRoy mentioned
    b. to have its settings defined in server/settings.py, note, it should be defined inside publish. e.g.
    class BasicEnabledStatesModel(BaseSettingsModel):
        enabled: bool = SettingsField(True, title="Enabled")
        optional: bool = SettingsField(True, title="Optional")
        active: bool = SettingsField(True, title="Active")
    
    
    class PublishPluginsModel(BaseSettingsModel):
        IntegrateNextcloudShare: BasicEnabledStatesModel= SettingsField(
            default_factory=BasicEnabledStatesModel,
            title="NextCloud share"
        )
    
    class NextCloudSettings(BaseSettingsModel):
        .
        .
        .
        publish: PublishPluginsModel = SettingsField(
            default_factory=PublishPluginsModel,
            title="Publish"
        )
    
    DEFAULT_VALUES = { 
    

After updating the settings and file location.
I was able to see the plugin in details tab as @BigRoy mentioned earlier.

2 Likes

nothing

this was just copied from kitsu addon

Okey, no problem it was just my testings I didnt know that this will go so far)) but i, very new to get and programing at that level so its not clear enough( but ill do my best

i just thought that this packer is standart so grabbed it from clockify because kitsu’s was not working

SOOOOOOOO I’M REWORKING IT NOW FOLLOWING YOURS SUGGESTIONS AND POSTING IT BACK

but one thing : I’m tryng to find method to modift {comment} so kitsu will just pickup my modified {comment} or to store url in tocken like {comment} to later use it in Kitsu note, any ideas?

This actually shouldn’t be too hard. Let’s investigate!

This Integrate Kitsu Note plug-in pushes the comments to Kitsu.

It has the annoying downside that it runs at the IntegratorOrder so you can’t sneak in there between the Integrator and the Integrate Kitsu Note to put your extra data in there. So we’ll unfortunately also need to tweak the Integrate Kitsu Note and bump the order to e.g. IntegratorOrder + 0.1.

Then as you can see here it uses instance.data["comment"] to construct the note’s text - we could just inject our test there!

So let’s make sure your plug-in that wants to add in your own link to the note runs after the integrator, but before the changed Kitsu Note integrator, e.g. setting an order of IntegratorOrder + 0.05 for yours.

Then all we’d need to do in your plug-in is:

# in your plug-in, so this is psuedocode
class YourPlugin(InstancePlugin):
    def process(self, instance):
        ...

        url = self.get_nextcloud_share_url(instance)

        # Add the URL to the comment for Kitsu note
        comment = instance.data.get("comment")
        if comment:
            # Add the URL at the end of the comment
            # with two line breaks (enters) above it.
            comment = f"{comment}\n\n{url}"
        else:
            comment = url

        instance.data["comment"] = comment

Kitsu Integrate Note custom format

Another approach is to use Kitsu Integrate Note’s custom format template from the studio settings.

That comment template could be e.g. {comment}{nextcloud_url} so that your plug-in would just have to set instance.data["nextcloud_url"] = url.

If that were to be replaced with AYON’s formatter instead of regex then it could also nicely support optional keys that also contain their own formatting, so that IF the next cloud URL is present then it could come by default with adding two lines:

{comment}<\n\n{nextcloud_url}>

Where the value between <> becomes optional formatting to only be included if the data nextcloud_url exists - but that would require more changes to AYON kitsu plug-in.

1 Like

Found some strange bug) i’ve updated a code and deleted all versions of plugin from ayon and wanted to push 0.0.1 version again and nothing! I’ve dowloaded my repo to work from “almost working stage” because i thought taht i maybe mess up something but again 0.0.1 version not lading to ayon, but 0.0.20 loading. I think some data has left in ayon after deleting, so i cant upload “old” version

for now I’m working still will write down progress!

I will try just to do this

IntegratorOrder - 0.05

Maybe it will work

YAY! now I can bebug! thanks!

@BigRoy @mustafa_jafar Guys what can it be when I dont see nuke using dev budle?

but i see it using normal launch

definetly it works))
image

1 Like

When not running dev mode, but running in production or staging mode the Launcher only downloads the latest addon versions if there is a new version. Even if you re-upload it to the server your local launcher does not care at all. It only checks, “do I already have a 0.0.1?” if so, it skips redownloading it.

You can force it to redownload it by downloading the locally cached version on your machine. On Windows it’s in %LOCALAPPDATA%\ynput\AYON\addons

The dev bundle uses its own “Dev” settings - it may be that the dev bundle settings does not have the application configured correctly?

I’m pretty sure you have customized e.g. your Nuke “Applications” settings to add a new version - which likely does NOT exist in your dev bundle.

Go to your dev bundle settings
image

Then copy settings from another bundle:
image

Take it from “source” Production and you’ll get to matching settings.

note 1: that each dev bundle, the production bundle and staging bundle each have their own settings

note 2: each addon version has its own settings. As soon as you upload a NEW version of an addon and you set it active for your bundle it will have the DEFAULT settings. You’ll have to “COPY SETTINGS” from the previous addon version if you want to have those again. This is another reason why developing in dev mode is a great help since you’re not re-uploading and tweaking settings the whole time, you can live update your code.

Note that with your integrator order at -0.05 you do not have access to the published path because the integrator hasn’t published it yet. :wink:

1 Like

everywhere nuke is pressent resolve shown like normal

I’ll find propper value)))

Thanks it helped, contionuing

1 Like

@timsergeeff
Man, I’m happy for you!
Great job and keep tinkering

Also, good to note - that Pyblish plug-ins “refresh” on reset of the publisher. So you don’t need to restart anything for changes to the Pyblish plug-ins to be picked up. Just change code, reset publisher, and publish.

Of course any Python dependencies loaded into the running plug-in will not be reloaded, but in that case if you know how to (ab)use it importlib.reload can get you far without needing any application restarts. :wink:

1 Like

I feel like that i cant refer to secrets when publishing am i right?