UNPKG

17 kBPlain TextView Raw
1#!/usr/bin/env node
2// Concepto TI DSL LIVE
3/*
4- inicialmente asociado a ti.dsl (hacer agnostico despues)
5
61. saludar en consola (todo hacer config.json para considerar opciones o usar argumentos)
7 1.1 se debe ejecutar en la carpeta del proyecto donde estan los archivos 'live_*.json' :: OK!
82. se debe conectar (o intentar cada x segundos) a Concepto en modo Live (usando smart.js->bridge) :: OK!
93. se debe conectar (o intentar cada x segundos) a TiShadow socket (puerto 3000) (usando ts.js)
104. cuando el punto 4 y 5 esten ok:
11 4.1 debe leer archivo app/live_properties.json de proyecto actual y dejar en memoria. :: OK!
12 4.2 debe leer archivo app/live_styles.json de proyecto actual y dejar en memoria.
135. leer cada transaccion y llamar funcion procesarTransaccion(nodo) :: OK!
14 5.1 ver si nodeid existe como llave en live_properties de memoria (4.2). :: OK!
15 5.2 ve si atributo tiene match en registro identificado de live_properties del punto 5.1 :: OK!
16 5.3 reemplazar de contenidos texto 'nodeid' por valor de nuestro nodo (getSpy('nodeid').) y dejar string en temporal. :: OK!
17 5.3 si tiene llave 'before_set', asignar valor de atributo a var temp 'setvalue' y asignar a string a enviar. :: OK!
18 5.4 si tiene llave 'set' asignar contenido a string a enviar: ej: getSpy('nodeid').setTitle(setvalue).. :: OK!
19*/
20
21var bridge = require('./smart');
22var tis = require('./ts.js');
23
24var apple = require('./apple');
25
26var notifier = require('node-notifier');
27var watch = require('node-watch'); // watched file changes.
28
29var jsonfile = require('jsonfile')
30var fs = require('fs');
31var path = require('path');
32var cheerio = require('cheerio');
33var _colors = require('colors');
34var pkginfo = require('pkginfo')(module);
35
36var args = process.argv.slice(2), cwd = '', lprop = '';
37var nodes = {}, nodes_meta = {}, last_cmd = '';
38var $;
39// define here all files that must be read (key)->live_key.json
40var _live = {
41 properties : {},
42 //style : {}
43};
44var _config = {
45 title: 'Concepto DSL Live Mode',
46 dsl: 'ti.dsl',
47 ti_connected: false,
48 live_retry_delay: 2000
49};
50
51if (args.length>0) {
52 // tiene un argumento (directorio del proyecto)
53 cwd = args[0];
54} else {
55 // no tiene un argumento, intentamos usar directorio en donde estamos
56 cwd = process.cwd();
57}
58
59// SEND WELCOME MESSAGE
60var _say_app_name = cwd.split(path.sep)[directory.split(path.sep).length-1].split('_').join(' ').split('-').join(' ');
61consoleTitle('Concepto DSL Live Mode Bridge :: ver '+module.exports.version,'yellow','magenta');
62apple.say('Laiv iniciado para aplicacion '+_say_app_name);
63
64// READ DSL AND JSON FILES
65loadLive(cwd, function(){
66 // read json files
67 loadJSON(cwd);
68});
69
70var _startAll = function() {
71 if (last_cmd!='_startAll') {
72 console.log('bridge:on'.underline.green);
73 // if live on, execute update on json properties : 14-1-2016 PSB
74 loadJSON(cwd);
75 // TI SHADOW SOCKET
76 tis.disconnect();
77 var socket = tis.connect(
78 function(e) {
79 //console.log("shadow:connected.\n".green);
80 _config.ti_connected = true;
81 // try to start Concepto Live Bridge
82 bridge.init(function(){});
83 //
84 },
85 function(msg) {
86 console.log("shadow:message received:".green,msg);
87 },
88 function() {
89 // on error
90 //console.log("shadow:disconnected.\n".red);
91 _config.ti_connected = false;
92 }
93 );
94 last_cmd = '_startAll';
95 }
96};
97
98var _stopAll = function() {
99 if (last_cmd!='_stopAll') {
100 console.log('bridge:off'.underline.red+'\n\n');
101 if (_config.ti_connected==true) {
102 // desconectamos ti_shadow
103 tis.disconnect();
104 //console.log("shadow:disconnected.\n\n".green);
105 }
106 last_cmd = '_stopAll';
107 }
108};
109
110// MONITOR OPEN PORTS
111var portastic = require('portastic');
112var monitor = new portastic.Monitor([3000,9000]);
113var _live_running = false, _ts_running = false;
114
115monitor.on('close', function(port) {
116 // seems monitos close/open are inverted.
117 if (port==9000) {
118 _live_running = true;
119 //console.log('concepto live seems running');
120 } else {
121 // tishadow server is online
122 _ts_running = true;
123 //console.log('tishadow seems running');
124 }
125 // only run if both ports are opened
126 if (_live_running==true && _ts_running==true) {
127 //console.log('both servers seems running: calling startAll');
128 _startAll();
129 }
130});
131monitor.on('open', function(port) {
132 if (port==9000) {
133 // concepto mode live is deactivated
134 _live_running = false;
135 //console.log('concepto live seems stop');
136 } else {
137 // tishadow server is offline
138 _ts_running = false;
139 //console.log('tishadow seems stop');
140 }
141 //
142 _stopAll();
143});
144
145// WATCH FILE CHANGES (ti.dsl and live_*.json)
146var files_to_watch = [path.join(cwd,'ti.dsl')];
147for (var el in _live) {
148 files_to_watch.push('live_'+el+'.json');
149 var ltmp = path.join(cwd, 'live_'+el+'.json');
150}
151watch(files_to_watch, function(file) {
152 // we should call LoadLive(cwd);
153 console.log('watch:file:'.grey+file.yellow+', changed.');
154 if (file.indexOf('.json')!=-1) {
155 loadJSON(cwd);
156 console.log('watch:json reloaded');
157 } else {
158 loadLive(cwd, function(){}, function(){});
159 console.log('watch:dsl reloaded');
160 }
161});
162
163
164// CONCEPTO DSL LIVE MODE (bridge) events
165var slow_ping = 0;
166bridge.on({
167 '_error' : function(error) {
168 // error connecting to concepto - bridge
169 //console.log('live:error connecting:retrying in 3 seconds'.grey);
170 /*setTimeout(function(){
171 bridge.destroy();
172 bridge.init(function(){});
173 }, 3000);*/
174 },
175 'who_are_you' : function(data) {
176 bridge.send.login('nexo');
177 console.log('live:login sent'.yellow);
178 // Concepto DSL Bridge Ready
179 apple.say('Laiv conectado.');
180 console.log('live:welcome'.green);
181 notifier.notify({
182 'title': _config.title,
183 'message': 'Bridge Activated',
184 sound: false
185 });
186 },
187 'user_information' : function(data) {
188 slow_ping++;
189 if (slow_ping%5==0) {
190 console.log('live:ping ..'.grey);
191 }
192 },
193 'welcome' : function(data) {
194 //console.log('live:welcome:'.green,data);
195 },
196 'goodbye' : function(data) {
197 apple.say('laiv desconectado.');
198 console.log('live:goodbye'.yellow);
199 },
200 'transaction' : function(data) {
201 if (bridge.getLastID()!=data['$'].id) {
202 // solo ejecutamos transaccion si no fuimos nosotros mismos.
203 bridge.parseXml(data['$'].do_action, function(tag, js) {
204 var nodeid = '';
205 if ('$' in js && 'node' in js['$']) nodeid = js['$'].node;
206 if (nodeid!='') {
207 var accion = tag.split('_').join(' ');
208 switch(tag) {
209 case 'insert_attribute_elementary_action':
210 case 'set_attribute_name_elementary_action':
211 var _row = ('row' in js.$)? eval(js.$.row) : 0;
212 //console.log('fila en donde quiero insertar atributo:'+_row);
213 if (nodeid in nodes==false) nodes[nodeid] = [];
214 if (!nodes[nodeid][_row]) nodes[nodeid][_row]={};
215 nodes[nodeid][_row].name = js.$.name;
216 break;
217 case 'edit_node_action':
218 case 'set_attribute_value_elementary_action':
219 if ('text' in js) {
220 // se solicito modificar el nodo: text (usamos nodes_meta)
221 if (nodeid in nodes_meta==false) nodes_meta[nodeid] = { _text:'', _color:'', _bgcolor:'', _icons:'' };
222 if (js.text[0].indexOf('<img')!=-1) {
223 // nodo cambio de imagen: done 14-1-2016
224 var che_img = cheerio.load(js.text[0], { recognizeSelfClosing:true });
225 var just_img = che_img('img[src]').attr('src');
226 var full_img = path.join(cwd,just_img);
227 if (fileExists(full_img)==true) {
228 nodes_meta[nodeid]._image = fs.readFileSync(full_img).toString('base64');
229 procesarTransaccion('nodes_meta',nodeid,nodes_meta[nodeid],'_image', nodes_meta[nodeid]._image);
230 } else {
231 console.log('changed image for nodeid '+nodeid+' was not found on disk ('+full_img+')');
232 }
233
234 } else {
235 // nodo de texto simple
236 nodes_meta[nodeid]._text = js.text[0];
237 procesarTransaccion('nodes_meta',nodeid,nodes_meta[nodeid],'_text', js.text[0]);
238 //console.log('cambiaron meta datos de nodo: '+nodeid,nodes_meta[nodeid]);
239 }
240 } else {
241 // asigna valor a atributo ya existente: da value
242 var _row = ('row' in js.$)? eval(js.$.row) : 0;
243 //console.log('fila en donde quiero modificar atributo:'+_row);
244 if (nodeid in nodes==false) nodes[nodeid] = [];
245 if (!nodes[nodeid][_row]) nodes[nodeid][_row]={};
246 nodes[nodeid][_row].value = js.value[0];
247 procesarTransaccion('nodes',nodeid,nodes[nodeid][_row],nodes[nodeid][_row].name, js.value[0]);
248 }
249 break;
250 case 'delete_attribute_elementary_action':
251 // eliminamos el atributo row indicado
252 var _row = ('row' in js.$)? eval(js.$.row) : 0;
253 if (nodeid in nodes==false) nodes[nodeid] = [];
254 if (nodes[nodeid][_row]) nodes[nodeid].splice(_row,1);
255 break;
256 case 'node_color_format_action':
257 // nodo cambio de color, lo definimos como un nodes_meta atributo _color
258 if (nodeid in nodes_meta==false) nodes_meta[nodeid] = { _text:'', _color:'', _bgcolor:'', _icons:'' };
259 nodes_meta[nodeid]._color = js.$.color;
260 procesarTransaccion('nodes_meta',nodeid,nodes_meta[nodeid],'_color',js.$.color);
261 //console.log('cambiaron meta datos de nodo: '+nodeid,nodes_meta[nodeid]);
262 break;
263 case 'node_background_color_format_action':
264 // nodo cambio de bgcolor
265 if (nodeid in nodes_meta==false) nodes_meta[nodeid] = { _text:'', _color:'', _bgcolor:'', _icons:'' };
266 nodes_meta[nodeid]._bgcolor = js.$.color;
267 procesarTransaccion('nodes_meta',nodeid,nodes_meta[nodeid],'_bgcolor',js.$.color);
268 //console.log('cambiaron meta datos de nodo: '+nodeid,nodes_meta[nodeid]);
269 break;
270 default:
271 console.log('switch aun no implementado:' + tag.red);
272 logfull('*** ACCION '+tag+' SOLICITADA SOBRE NODE ID: '+nodeid+':',js);
273 break;
274 }
275 //console.log('nodo modificado ('.yellow+nodeid.green+'):'.yellow,nodes[nodeid]);
276 //logfull('*** ACCION '+tag+' SOLICITADA SOBRE NODE ID: '+nodeid+':',js);
277 //apple.say('Accion '+(tag.split('_').join(' '))+' solicitada en nodo.');
278 }
279 });
280 }
281 //console.log('dump de transaction:',data);
282 },
283 '*' : function(data, comando) {
284 console.log('*** EVENTO GENERICO DETECTADO (cmd:'+comando+'): ***', data);
285 }
286});
287
288// *****************
289// helper methods
290// *****************
291 function procesarTransaccion(type, nodeid, attributes, changed, value) {
292 // type: nodes, nodes_meta
293 // changed: attributes names that changed
294 console.log('process ('.green+_colors.white(nodeid)+'):'.green+_colors.yellow(type)+'('+_colors.red(changed)+'):'.yellow,attributes);
295 // traducimos a codigo para shadow
296 if (nodeid in _live.properties && changed in _live.properties[nodeid]) {
297 var _live_prop = _live.properties[nodeid][changed];
298 //logfull('dsl.cfc data for node:'.yellow, _live_prop);
299 var _code = '';
300 if (!isNaN(value)) {
301 _code = "var setvalue = "+value+";\n";
302 } else if (value.indexOf("'")!=-1 || value.indexOf('"')!=-1) {
303 // value usa comillas (dejamos tal cual)
304 _code = "var setvalue = "+value+";\n";
305 } else {
306 _code = "var setvalue = '"+value+"';\n";
307 }
308 if (value!='') {
309 // solo procesar si valor no esta completamente en blanco.
310 if ('BEFORE_SET' in _live_prop) _code += _live_prop['BEFORE_SET'];
311 if ('before_set' in _live_prop) _code += _live_prop['before_set'];
312 if ('SET' in _live_prop) _code += 'getSpy("' + nodeid + '").' + _live_prop['SET'] + '(setvalue);';
313 if ('set' in _live_prop) _code += 'getSpy("' + nodeid + '").' + _live_prop['set'] + '(setvalue);';
314 if ('CUSTOMSET' in _live_prop) _code += _live_prop['CUSTOMSET'] + '(setvalue);';
315 if ('customset' in _live_prop) _code += _live_prop['customset'] + '(setvalue);';
316 _code = _code.split('setvalue').join('_setvalue');
317 _code = _code.split('nodeid').join('getSpy("'+nodeid+'")');
318 _code = _code.split('\t').join(''); // eliminamos los tabs
319 console.log('codigo a enviar a tishadow:',_code);
320 _code = _code.split('"').join("'");
321 _code = _code.split("'").join("\'");
322 _code = _code.split("\n").join(" ");
323 var _real = 'Alloy.Globals.live_apply("'+nodeid+'_'+changed+'","'+_code+'");';
324 console.log('real enviando:',_real);
325 if (_config.ti_connected==true) {
326 tis.send(_real);
327 }
328 }
329 } else if (nodeid in _live.properties) {
330 console.log('nodo tiene propiedades, pero atributo no esta dentro de capacidades');
331 } else {
332 //console.log('live properties?',_live.properties);
333 }
334}
335
336function loadJSON(directory) {
337 // loads live_*.json into var '_live.*'
338 console.log('loading json.');
339 for (var el in _live) {
340 var ltmp = path.join(directory, 'live_'+el+'.json');
341 _live[el] = null; // clean the obj first to be overwritten :14/1/2016 PSB
342 if (fileExists(ltmp)==true) {
343 console.log('json file loaded');
344 _live[el] = jsonfile.readFileSync(ltmp,'utf-8');
345 }
346 }
347}
348
349function loadLive(directory, onSuccess, onError) {
350 var dsl_map, padre_id = '', tmp_color = '', tmp_bgcolor = '', tmp_link = '', e_ok = true, _app_name = '';
351 // loads the dsl file, process it into 'nodes' struct var
352 if (fileExists(path.join(directory,_config.dsl))) {
353 _app_name = directory.split(path.sep)[directory.split(path.sep).length-1].toUpperCase();
354 console.log('Processing file '.yellow+_config.dsl.toUpperCase().green + ' ('.white+_app_name.cyan+')'.white);
355 dsl_map = fs.readFileSync(path.join(cwd,_config.dsl),{ encoding:'utf-8' });
356 nodes = {}; // reset nodes file
357 nodes_meta = {}; // reset nodes_meta file
358 // process dsl file into 'nodes'
359 $ = cheerio.load(dsl_map, { recognizeSelfClosing:true });
360 $('node').each(function(index_a,element_a) {
361 padre_id = $(this).attr('id');
362 if (padre_id in nodes==false) nodes[padre_id] = [];
363 if (padre_id in nodes_meta==false) nodes_meta[padre_id] = { _text:'', _color:'', _bgcolor:'', _icons:'' };
364 // assign node metadata (_color, _bgcolor, _link, _text, _icons)
365 if ($(this).attr('text')) nodes_meta[padre_id]._text = $(this).attr('text');
366 if ($(this).attr('color')) nodes_meta[padre_id]._color = $(this).attr('color');
367 if ($(this).attr('background_color')) nodes_meta[padre_id]._bgcolor = $(this).attr('background_color');
368 var _icons = [];
369 $(this).find('icon').each(function(i,e){
370 if ($(e).parent('node').attr('id')==padre_id) {
371 _icons.push($(e).attr('builtin'));
372 }
373 });
374 _icons = _icons.join(',');
375 nodes_meta[padre_id]._icons = _icons;
376 // assign node attributes
377 $(this).find('attribute').each(function(index,element) {
378 if ($(element).parent('node').attr('id')==padre_id) {
379 nodes[padre_id][nodes[padre_id].length] = { name: $(element).attr('name'), value: $(element).attr('value') };
380 }
381 });
382 });
383 if (onSuccess && typeof onSuccess === 'function') {
384 onSuccess();
385 }
386 } else {
387 console.log(_colors.red('FATAL ERROR:\n')+_colors.cyan('NO APP DSL found on ')+_colors.yellow(directory)+'\n\n');
388 process.exit(2);
389 e_ok = false;
390 if (onError && typeof onError === 'function') {
391 onError('file '+_config.dsl+' does not exist on directory '+directory);
392 }
393 }
394}
395
396var searchAttributesOf = function($, nodeid) {
397 var attrs = [], count = 0, node = {};
398 if ($) {
399 $('node[ID='+nodeid+'] attribute').each(function(index,element) {
400 node[index] = { name: $(this).attr('NAME'), value: $(this).attr('VALUE') };
401 });
402 }
403 return node;
404};
405
406var logfull = function(msg, data) {
407 console.log(msg, JSON.stringify(data));
408};
409
410function consoleTitle(message, color, bordercolor) {
411 var max_len = 0, to_show = '', to_show_open = '', to_show_close = '',
412 left_right_chars = '**',
413 left_right_space = 2,
414 left_right_margin = left_right_chars.length+left_right_space;
415 if (message.indexOf('\n')!=-1) {
416 // multiple lines message
417 for (var lines in message.split('\n')) {
418 if (message.split('\n')[lines].length > max_len) {
419 max_len = message.split('\n')[lines].length;
420 }
421 }
422 } else {
423 // single line message
424 max_len = message.length;
425 to_show_open = Array(max_len+(left_right_margin*2)).join('*') + '\n';
426 to_show_open += left_right_chars + Array(left_right_space).join(' ');
427 if (bordercolor) to_show_open = _colors[bordercolor](to_show_open);
428 if (color) {
429 to_show = _colors[color](message);
430 } else {
431 to_show = message;
432 }
433 to_show_close += Array(left_right_space).join(' ') + left_right_chars + '\n';
434 to_show_close += Array(max_len+(left_right_margin*2)).join('*') + '\n';
435 if (bordercolor) to_show_close = _colors[bordercolor](to_show_close);
436 // glue message
437 to_show = to_show_open + to_show + to_show_close;
438 }
439 console.log(to_show);
440}
441
442function fileExists(filePath)
443{
444 try
445 {
446 return fs.statSync(filePath).isFile();
447 }
448 catch (err)
449 {
450 return false;
451 }
452}
453
454function getHash(str) {
455 var hash = 5381,
456 i = str.length
457
458 while(i)
459 hash = (hash * 33) ^ str.charCodeAt(--i)
460
461 /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
462 * integers. Since we want the results to be always positive, convert the
463 * signed int to an unsigned by doing an unsigned bitshift. */
464 return hash >>> 0;
465}
\No newline at end of file