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)
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
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.
Inside the launched applications, open the publisher. Then check the “Details” tab.
Check the “Crashed Plugins” top right. Is your plug-in listed there? If so, you can expand it to get to the relevant error.
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.
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).
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.)
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.
Repo Name: Would you like to follow our naming convention e.g. ayon-nextcloud instead of nextcloud_ayon_dev ?
I think creating a dev branch could be better than using dev in the repo’s name.
Your addon in this line expects your publish folder to be inside plugins folder.
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.
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?
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.
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!
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
Then copy settings from another bundle:
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.
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.