mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
545 lines
16 KiB
JavaScript
545 lines
16 KiB
JavaScript
/* global QTcpSocket, QByteArray, QDataStream, QTimer, QTextCodec, QIODevice, QApplication, include */
|
|
/* global QTcpSocket, QByteArray, QDataStream, QTimer, QTextCodec, QIODevice, QApplication, include */
|
|
/*
|
|
Avalon Harmony Integration - Client
|
|
-----------------------------------
|
|
|
|
This script implements client communication with Avalon server to bridge
|
|
gap between Python and QtScript.
|
|
|
|
*/
|
|
/* jshint proto: true */
|
|
var LD_OPENHARMONY_PATH = System.getenv('LIB_OPENHARMONY_PATH');
|
|
LD_OPENHARMONY_PATH = LD_OPENHARMONY_PATH + '/openHarmony.js';
|
|
LD_OPENHARMONY_PATH = LD_OPENHARMONY_PATH.replace(/\\/g, "/");
|
|
include(LD_OPENHARMONY_PATH);
|
|
this.__proto__['$'] = $;
|
|
|
|
function Client() {
|
|
var self = this;
|
|
/** socket */
|
|
self.socket = new QTcpSocket(this);
|
|
/** receiving data buffer */
|
|
self.received = '';
|
|
self.messageId = 1;
|
|
self.buffer = new QByteArray();
|
|
self.waitingForData = 0;
|
|
|
|
|
|
/**
|
|
* pack number into bytes.
|
|
* @function
|
|
* @param {int} num 32 bit integer
|
|
* @return {string}
|
|
*/
|
|
self.pack = function(num) {
|
|
var ascii='';
|
|
for (var i = 3; i >= 0; i--) {
|
|
var hex = ((num >> (8 * i)) & 255).toString(16);
|
|
if (hex.length < 2){
|
|
ascii += "0";
|
|
}
|
|
ascii += hex;
|
|
}
|
|
return ascii;
|
|
};
|
|
|
|
/**
|
|
* unpack number from string.
|
|
* @function
|
|
* @param {string} numString bytes to unpack
|
|
* @return {int} 32bit unsigned integer.
|
|
*/
|
|
self.unpack = function(numString) {
|
|
var result=0;
|
|
for (var i = 3; i >= 0; i--) {
|
|
result += numString.charCodeAt(3 - i) << (8 * i);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* prettify json for easier debugging
|
|
* @function
|
|
* @param {object} json json to process
|
|
* @return {string} prettified json string
|
|
*/
|
|
self.prettifyJson = function(json) {
|
|
var jsonString = JSON.stringify(json);
|
|
return JSON.stringify(JSON.parse(jsonString), null, 2);
|
|
};
|
|
|
|
/**
|
|
* Log message in debug level.
|
|
* @function
|
|
* @param {string} data - message
|
|
*/
|
|
self.logDebug = function(data) {
|
|
var message = typeof(data.message) != 'undefined' ? data.message : data;
|
|
MessageLog.trace('(DEBUG): ' + message.toString());
|
|
};
|
|
|
|
/**
|
|
* Log message in info level.
|
|
* @function
|
|
* @param {string} data - message
|
|
*/
|
|
self.logInfo = function(data) {
|
|
var message = typeof(data.message) != 'undefined' ? data.message : data;
|
|
MessageLog.trace('(DEBUG): ' + message.toString());
|
|
};
|
|
|
|
/**
|
|
* Log message in warning level.
|
|
* @function
|
|
* @param {string} data - message
|
|
*/
|
|
self.logWarning = function(data) {
|
|
var message = typeof(data.message) != 'undefined' ? data.message : data;
|
|
MessageLog.trace('(INFO): ' + message.toString());
|
|
};
|
|
|
|
/**
|
|
* Log message in error level.
|
|
* @function
|
|
* @param {string} data - message
|
|
*/
|
|
self.logError = function(data) {
|
|
var message = typeof(data.message) != 'undefined' ? data.message : data;
|
|
MessageLog.trace('(ERROR): ' +message.toString());
|
|
};
|
|
|
|
/**
|
|
* Show message in Harmony GUI as popup window.
|
|
* @function
|
|
* @param {string} msg - message
|
|
*/
|
|
self.showMessage = function(msg) {
|
|
MessageBox.information(msg);
|
|
};
|
|
|
|
/**
|
|
* Implement missing setTimeout() in Harmony.
|
|
* This calls once given function after some interval in milliseconds.
|
|
* @function
|
|
* @param {function} fc function to call.
|
|
* @param {int} interval interval in milliseconds.
|
|
* @param {boolean} single behave as setTimeout or setInterval.
|
|
*/
|
|
self.setTimeout = function(fc, interval, single) {
|
|
var timer = new QTimer();
|
|
if (!single) {
|
|
timer.singleShot = true; // in-case if setTimout and false in-case of setInterval
|
|
} else {
|
|
timer.singleShot = single;
|
|
}
|
|
timer.interval = interval; // set the time in milliseconds
|
|
timer.singleShot = true; // in-case if setTimout and false in-case of setInterval
|
|
timer.timeout.connect(this, function(){
|
|
fc.call();
|
|
});
|
|
timer.start();
|
|
};
|
|
|
|
/**
|
|
* Process received request. This will eval received function and produce
|
|
* results.
|
|
* @function
|
|
* @param {object} request - received request JSON
|
|
* @return {object} result of evaled function.
|
|
*/
|
|
self.processRequest = function(request) {
|
|
var mid = request.message_id;
|
|
if (typeof request.reply !== 'undefined') {
|
|
self.logDebug('['+ mid +'] *** received reply.');
|
|
return;
|
|
}
|
|
self.logDebug('['+ mid +'] - Processing: ' + self.prettifyJson(request));
|
|
var result = null;
|
|
|
|
if (typeof request.script !== 'undefined') {
|
|
self.logDebug('[' + mid + '] Injecting script.');
|
|
try {
|
|
eval.call(null, request.script);
|
|
} catch (error) {
|
|
self.logError(error);
|
|
}
|
|
} else if (typeof request["function"] !== 'undefined') {
|
|
try {
|
|
var _func = eval.call(null, request["function"]);
|
|
|
|
if (request.args == null) {
|
|
result = _func();
|
|
} else {
|
|
result = _func(request.args);
|
|
}
|
|
} catch (error) {
|
|
result = 'Error processing request.\n' +
|
|
'Request:\n' +
|
|
self.prettifyJson(request) + '\n' +
|
|
'Error:\n' + error;
|
|
}
|
|
} else {
|
|
self.logError('Command type not implemented.');
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* This gets called when socket received new data.
|
|
* @function
|
|
*/
|
|
self.onReadyRead = function() {
|
|
var currentSize = self.buffer.size();
|
|
self.logDebug('--- Receiving data ( '+ currentSize + ' )...');
|
|
var newData = self.socket.readAll();
|
|
var newSize = newData.size();
|
|
self.buffer.append(newData);
|
|
self.logDebug(' - got ' + newSize + ' bytes of new data.');
|
|
self.processBuffer();
|
|
};
|
|
|
|
/**
|
|
* Process data received in buffer.
|
|
* This detects messages by looking for header and message length.
|
|
* @function
|
|
*/
|
|
self.processBuffer = function() {
|
|
var length = self.waitingForData;
|
|
if (self.waitingForData == 0) {
|
|
// read header from the buffer and remove it
|
|
var header_data = self.buffer.mid(0, 6);
|
|
self.buffer = self.buffer.remove(0, 6);
|
|
|
|
// convert header to string
|
|
var header = '';
|
|
for (var i = 0; i < header_data.size(); ++i) {
|
|
// data in QByteArray come out as signed bytes.
|
|
var unsigned = header_data.at(i) & 0xff;
|
|
header = header.concat(String.fromCharCode(unsigned));
|
|
}
|
|
|
|
// skip 'AH' and read only length, unpack it to integer
|
|
header = header.substr(2);
|
|
length = self.unpack(header);
|
|
}
|
|
|
|
var data = self.buffer.mid(0, length);
|
|
self.logDebug('--- Expected: ' + length + ' | Got: ' + data.size());
|
|
if (length > data.size()) {
|
|
// we didn't received whole message.
|
|
self.waitingForData = length;
|
|
self.logDebug('... waiting for more data (' + length + ') ...');
|
|
return;
|
|
}
|
|
self.waitingForData = 0;
|
|
self.buffer.remove(0, length);
|
|
|
|
for (var j = 0; j < data.size(); ++j) {
|
|
self.received = self.received.concat(String.fromCharCode(data.at(j)));
|
|
}
|
|
|
|
// self.logDebug('--- Received: ' + self.received);
|
|
var to_parse = self.received;
|
|
var request = JSON.parse(to_parse);
|
|
var mid = request.message_id;
|
|
// self.logDebug('[' + mid + '] - Request: ' + '\n' + JSON.stringify(request));
|
|
self.logDebug('[' + mid + '] Received.');
|
|
|
|
request.result = self.processRequest(request);
|
|
self.logDebug('[' + mid + '] Processing done.');
|
|
self.received = '';
|
|
|
|
if (request.reply !== true) {
|
|
request.reply = true;
|
|
self.logDebug('[' + mid + '] Replying.');
|
|
self._send(JSON.stringify(request));
|
|
}
|
|
|
|
if ((length < data.size()) || (length < self.buffer.size())) {
|
|
// we've received more data.
|
|
self.logDebug('--- Got more data to process ...');
|
|
self.processBuffer();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Run when Harmony connects to server.
|
|
* @function
|
|
*/
|
|
self.onConnected = function() {
|
|
self.logDebug('Connected to server ...');
|
|
self.lock = false;
|
|
self.socket.readyRead.connect(self.onReadyRead);
|
|
var app = QCoreApplication.instance();
|
|
|
|
app.avalonClient.send(
|
|
{
|
|
'module': 'openpype.lib',
|
|
'method': 'emit_event',
|
|
'args': ['application.launched']
|
|
}, false);
|
|
};
|
|
|
|
self._send = function(message) {
|
|
/** Harmony 21.1 doesn't have QDataStream anymore.
|
|
|
|
This means we aren't able to write bytes into QByteArray so we had
|
|
modify how content length is sent do the server.
|
|
Content length is sent as string of 8 char convertible into integer
|
|
(instead of 0x00000001[4 bytes] > "000000001"[8 bytes]) */
|
|
var codec_name = new QByteArray().append("UTF-8");
|
|
|
|
var codec = QTextCodec.codecForName(codec_name);
|
|
var msg = codec.fromUnicode(message);
|
|
var l = msg.size();
|
|
var header = new QByteArray().append('AH').append(self.pack(l));
|
|
var coded = msg.prepend(header);
|
|
|
|
self.socket.write(coded);
|
|
self.logDebug('Sent.');
|
|
};
|
|
|
|
self.waitForLock = function() {
|
|
if (self._lock === false) {
|
|
self.logDebug('>>> Unlocking ...');
|
|
return;
|
|
} else {
|
|
self.logDebug('Setting timer.');
|
|
self.setTimeout(self.waitForLock, 300);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Send request to server.
|
|
* @param {object} request - json encoded request.
|
|
*/
|
|
self.send = function(request) {
|
|
request.message_id = self.messageId;
|
|
if (typeof request.reply == 'undefined') {
|
|
self.logDebug("[" + self.messageId + "] sending:\n" + self.prettifyJson(request));
|
|
}
|
|
self._send(JSON.stringify(request));
|
|
};
|
|
|
|
/**
|
|
* Executed on disconnection.
|
|
*/
|
|
self.onDisconnected = function() {
|
|
self.socket.close();
|
|
};
|
|
|
|
/**
|
|
* Disconnect from server.
|
|
*/
|
|
self.disconnect = function() {
|
|
self.socket.close();
|
|
};
|
|
|
|
self.socket.connected.connect(self.onConnected);
|
|
self.socket.disconnected.connect(self.onDisconnected);
|
|
}
|
|
|
|
/**
|
|
* Entry point, creating Avalon Client.
|
|
*/
|
|
function start() {
|
|
var self = this;
|
|
/** hostname or ip of server - should be localhost */
|
|
var host = '127.0.0.1';
|
|
/** port of the server */
|
|
var port = parseInt(System.getenv('AVALON_HARMONY_PORT'));
|
|
|
|
// Attach the client to the QApplication to preserve.
|
|
var app = QCoreApplication.instance();
|
|
|
|
if (app.avalonClient == null) {
|
|
app.avalonClient = new Client();
|
|
app.avalonClient.socket.connectToHost(host, port);
|
|
}
|
|
var mainWindow = null;
|
|
var widgets = QApplication.topLevelWidgets();
|
|
for (var i = 0 ; i < widgets.length; i++) {
|
|
if (widgets[i] instanceof QMainWindow){
|
|
mainWindow = widgets[i];
|
|
}
|
|
}
|
|
var menuBar = mainWindow.menuBar();
|
|
var actions = menuBar.actions();
|
|
app.avalonMenu = null;
|
|
|
|
for (var i = 0 ; i < actions.length; i++) {
|
|
label = System.getenv('AVALON_LABEL');
|
|
if (actions[i].text == label) {
|
|
app.avalonMenu = true;
|
|
}
|
|
}
|
|
|
|
var menu = null;
|
|
if (app.avalonMenu == null) {
|
|
menu = menuBar.addMenu(System.getenv('AVALON_LABEL'));
|
|
}
|
|
// menu = menuBar.addMenu('Avalon');
|
|
|
|
/**
|
|
* Show creator
|
|
*/
|
|
self.onCreator = function() {
|
|
app.avalonClient.send({
|
|
'module': 'openpype.hosts.harmony.api.lib',
|
|
'method': 'show',
|
|
'args': ['creator']
|
|
}, false);
|
|
};
|
|
|
|
var action = menu.addAction('Create...');
|
|
action.triggered.connect(self.onCreator);
|
|
|
|
|
|
/**
|
|
* Show Workfiles
|
|
*/
|
|
self.onWorkfiles = function() {
|
|
app.avalonClient.send({
|
|
'module': 'openpype.hosts.harmony.api.lib',
|
|
'method': 'show',
|
|
'args': ['workfiles']
|
|
}, false);
|
|
};
|
|
if (app.avalonMenu == null) {
|
|
action = menu.addAction('Workfiles...');
|
|
action.triggered.connect(self.onWorkfiles);
|
|
}
|
|
|
|
/**
|
|
* Show Loader
|
|
*/
|
|
self.onLoad = function() {
|
|
app.avalonClient.send({
|
|
'module': 'openpype.hosts.harmony.api.lib',
|
|
'method': 'show',
|
|
'args': ['loader']
|
|
}, false);
|
|
};
|
|
// add Loader item to menu
|
|
if (app.avalonMenu == null) {
|
|
action = menu.addAction('Load...');
|
|
action.triggered.connect(self.onLoad);
|
|
}
|
|
|
|
/**
|
|
* Show Publisher
|
|
*/
|
|
self.onPublish = function() {
|
|
app.avalonClient.send({
|
|
'module': 'openpype.hosts.harmony.api.lib',
|
|
'method': 'show',
|
|
'args': ['publish']
|
|
}, false);
|
|
};
|
|
// add Publisher item to menu
|
|
if (app.avalonMenu == null) {
|
|
action = menu.addAction('Publish...');
|
|
action.triggered.connect(self.onPublish);
|
|
}
|
|
|
|
/**
|
|
* Show Scene Manager
|
|
*/
|
|
self.onManage = function() {
|
|
app.avalonClient.send({
|
|
'module': 'openpype.hosts.harmony.api.lib',
|
|
'method': 'show',
|
|
'args': ['sceneinventory']
|
|
}, false);
|
|
};
|
|
// add Scene Manager item to menu
|
|
if (app.avalonMenu == null) {
|
|
action = menu.addAction('Manage...');
|
|
action.triggered.connect(self.onManage);
|
|
}
|
|
|
|
/**
|
|
* Show Subset Manager
|
|
*/
|
|
self.onSubsetManage = function() {
|
|
app.avalonClient.send({
|
|
'module': 'openpype.hosts.harmony.api.lib',
|
|
'method': 'show',
|
|
'args': ['subsetmanager']
|
|
}, false);
|
|
};
|
|
// add Subset Manager item to menu
|
|
if (app.avalonMenu == null) {
|
|
action = menu.addAction('Subset Manager...');
|
|
action.triggered.connect(self.onSubsetManage);
|
|
}
|
|
|
|
/**
|
|
* Show Experimental dialog
|
|
*/
|
|
self.onExperimentalTools = function() {
|
|
app.avalonClient.send({
|
|
'module': 'openpype.hosts.harmony.api.lib',
|
|
'method': 'show',
|
|
'args': ['experimental_tools']
|
|
}, false);
|
|
};
|
|
// add Subset Manager item to menu
|
|
if (app.avalonMenu == null) {
|
|
action = menu.addAction('Experimental Tools...');
|
|
action.triggered.connect(self.onExperimentalTools);
|
|
}
|
|
|
|
// FIXME(antirotor): We need to disable `on_file_changed` now as is wreak
|
|
// havoc when "Save" is called multiple times and zipping didn't finished yet
|
|
/*
|
|
|
|
// Watch scene file for changes.
|
|
app.onFileChanged = function(path)
|
|
{
|
|
var app = QCoreApplication.instance();
|
|
if (app.avalonOnFileChanged){
|
|
app.avalonClient.send(
|
|
{
|
|
'module': 'avalon.harmony.lib',
|
|
'method': 'on_file_changed',
|
|
'args': [path]
|
|
},
|
|
false
|
|
);
|
|
}
|
|
|
|
app.watcher.addPath(path);
|
|
};
|
|
|
|
|
|
app.watcher = new QFileSystemWatcher();
|
|
scene_path = scene.currentProjectPath() +"/" + scene.currentVersionName() + ".xstage";
|
|
app.watcher.addPath(scenePath);
|
|
app.watcher.fileChanged.connect(app.onFileChanged);
|
|
app.avalonOnFileChanged = true;
|
|
*/
|
|
app.onFileChanged = function(path) {
|
|
// empty stub
|
|
return path;
|
|
};
|
|
}
|
|
|
|
function ensureSceneSettings() {
|
|
var app = QCoreApplication.instance();
|
|
app.avalonClient.send(
|
|
{
|
|
"module": "openpype.hosts.harmony.api",
|
|
"method": "ensure_scene_settings",
|
|
"args": []
|
|
},
|
|
false
|
|
);
|
|
}
|
|
|
|
function TB_sceneOpened()
|
|
{
|
|
start();
|
|
}
|