<commit_before>KityMinder.registerModule( "LayoutModule", function () {
	var defaultHeight = 25;
	//更新分支的高度信息
	var updateBranchHeight = function ( node, appendSide, root, isAdd, oldParent ) {
		var siblings = isAdd ? node.getParent().getChildren() : oldParent.getChildren();
		if ( !isAdd && ( siblings.length === 0 || ( !oldParent.getParent() && oldParent !== root ) ) ) {
			return false;
		}
		var parent = isAdd ? node : oldParent;
		if ( isAdd ) {
			do {
				var branchheight = parent.getData( appendSide + "Height" ) || parent.getData( "branchheight" ) || 0;
				if ( parent === root ) {
					parent.setData( appendSide + "Height", branchheight + defaultHeight + 10 );
				} else {
					parent.setData( "branchheight", branchheight + defaultHeight + 10 );
				}
				parent = parent.getParent();
			} while ( parent && ( parent.getChildren().length >= 2 || parent === root ) );
			if ( siblings.length === 1 ) {
				return false;
			} else {
				return true;
			}
		} else {
			var dec = node.getData( "branchheight" );
			console.log( dec );
			do {
				var branchheight2 = parent.getData( appendSide + "Height" ) || parent.getData( "branchheight" ) || 0;
				if ( parent === root ) {
					parent.setData( appendSide + "Height", branchheight2 - dec );
				} else {
					parent.setData( "branchheight", branchheight2 - dec );
				}
				parent = parent.getParent();
			} while ( parent );
			return true;
		}
	};
	var reAnalyze = function ( km, layerArray, appendSide ) {
		for ( var lv = 0; lv < layerArray.length; lv++ ) {
			var lvData = layerArray[ lv ];
			for ( var i = 0; i < lvData.length; i++ ) {
				var children = ( lv === 0 ? layerArray[ 1 ] : lvData[ i ].getChildren() );
				if ( !children || children.length === 0 ) continue;
				var branchheight = lvData[ i ].getData( appendSide + "Height" ) || lvData[ i ].getData( "branchheight" );
				var sY = lvData[ i ].getData( "y" ) + ( children[ 0 ].getData( "branchheight" ) - branchheight ) / 2;
				console.log( sY );
				for ( var j = 0; j < children.length; j++ ) {
					children[ j ].setData( "y", sY );
					var part1 = ( children[ j ].getData( "branchheight" ) - 10 ) / 2 + 10;
					var part2 = ( children[ j + 1 ] ? ( children[ j + 1 ].getData( "branchheight" ) - 10 ) / 2 : 0 );
					sY += ( part1 + part2 );
				}
				km.execCommand( "rendernode", children );
			}
		}
	};
	var createChildNode = function ( km, parent, index ) {
		var root = km.getRoot();
		var appendSide = parent.getData( "appendside" );
		var _node = new MinderNode();
		_node.setData( "branchheight", 0 );
		parent.insertChild( _node, index );

		_node.setData( "appendside", appendSide );
		if ( parent === root ) {
			var sidecount = root.getData( "sidecount" );
			if ( sidecount + 1 === 5 ) {
				root.setData( "sidecount", 0 );
				root.setData( "appendside", ( "leftright" ).replace( appendSide, "" ) );
			} else {
				root.setData( "sidecount", sidecount + 1 );
			}
		}
		var parentX = parent.getData( "x" );
		var parentWidth = parent.getRenderContainer().getWidth();

		switch ( appendSide ) {
		case "left":
			_node.setData( "x", parentX - parentWidth - 50 );
			_node.setData( "align", "right" );
			break;
		case "right":
			_node.setData( "x", parentX + parentWidth + 50 );
			_node.setData( "align", "left" );
			break;
		default:
			break;
		}

		var layer = parent.getData( "layer" ) + 1;
		var layerArray = root.getData( "layer" + appendSide );
		layerArray[ layer ] = layerArray[ layer ] || [];
		var layerData = layerArray[ layer ];
		var insertPos = 0;
		_node.setData( "layer", layer );

		//遍历层级链
		var getIndexList = function ( node ) {
			var indexList = [];
			var parent = node;
			do {
				indexList.push( parent.getIndex() );
				parent = parent.getParent();
			} while ( parent );
			return indexList.reverse();
		};

		//比较两个层级链的大小
		var indexLarger = function ( List1, List2 ) {
			var larger = true;
			for ( var i = 0; i < List1.length; i++ ) {
				if ( List1[ i ] == List2[ i ] ) {
					continue;
				}
				if ( List1[ i ] < List2[ i ] ) {
					larger = false;
				}
				break;
			}
			return larger;
		};

		//选定合适的位置插入节点
		for ( var l = layerData.length - 1; l >= 0; l-- ) {
			var nodeIndexList = getIndexList( _node );
			if ( !indexLarger( getIndexList( layerData[ l ] ), nodeIndexList ) ) {
				insertPos = l + 1;
				break;
			}
		}

		layerData.splice( insertPos, 0, _node );
		var reAnal = updateBranchHeight( _node, appendSide, root, true );
		//判断是重绘全部还是只是添加节点
		if ( reAnal ) {
			reAnalyze( km, layerArray, appendSide );
		} else {
			_node.setData( "y", _node.getParent().getData( "y" ) );
			km.execCommand( "rendernode", _node );
		}
		return _node;
	};

	var CreateChildNodeCommand = kity.createClass( "CreateChildNodeCommand", ( function () {
		return {
			base: Command,
			execute: createChildNode
		};
	} )() );

	var CreateSiblingNodeCommand = kity.createClass( "CreateSiblingNodeCommand", ( function () {
		return {
			base: Command,
			execute: function ( km, sibling ) {
				var parent = sibling.getParent();
				var index = sibling.getIndex() + 1;
				if ( parent ) {
					return createChildNode( km, parent, index );
				} else {
					return false;
				}
			}
		};
	} )() );

	var RemoveNodeCommand = kity.createClass( "RemoveNodeCommand", ( function () {

		return {
			base: Command,
			execute: function ( km, nodes ) {
				var root = km.getRoot();
				for ( var i = 0; i < nodes.length; i++ ) {
					var parent = nodes[ i ].getParent();
					if ( parent ) {
						var appendSide = nodes[ i ].getData( "appendside" );
						var layer = nodes[ i ].getData( "layer" );
						parent.removeChild( nodes[ i ] );
						var layerArray = root.getData( "layer" + appendSide );
						var layerData = layerArray[ layer ];
						//移除层结构中的node
						for ( var j = 0; j < layerData.length; j++ ) {
							if ( layerData[ j ] === nodes[ i ] ) {
								layerData.splice( j, 1 );
								break;
							}
						}
						var reAnal = updateBranchHeight( nodes[ i ], appendSide, root, false, parent );
						console.log( layerArray );
						if ( reAnal ) {
							reAnalyze( km, layerArray, appendSide );
						}
					}
				}
				this.setContentChanged( true );
			}
		};
	} )() );
	return {
		"commands": {
			"createchildnode": CreateChildNodeCommand,
			"createsiblingnode": CreateSiblingNodeCommand,
			"removenode": RemoveNodeCommand
		},

		"events": {

		}
	};
} );<commit_msg>changed add<commit_after>KityMinder.registerModule( "LayoutModule", function () {
	var defaultHeight = 25;
	//更新分支的高度信息
	var updateBranchHeight = function ( node, appendSide, root, isAdd, oldParent ) {
		var siblings = ( function () {
			if ( !isAdd ) {
				return oldParent.getChildren();
			} else if ( parent === root ) {
				return root.getData( "layer" + appendSide )[ 1 ];
			} else {
				return node.getParent().getChildren();
			}
		} )();
		console.log( siblings );
		if ( !isAdd && ( siblings.length === 0 || ( !oldParent.getParent() && oldParent !== root ) ) ) {
			return false;
		}
		var parent = isAdd ? node : oldParent;
		if ( isAdd ) {
			var add = ( ( siblings.length === 1 && node.getParent() !== root ) ? 0 : ( defaultHeight + 10 ) );
			console.log( add );
			do {
				var branchheight = parent.getData( appendSide + "Height" ) || parent.getData( "branchheight" ) || 0;
				if ( parent === root ) {
					parent.setData( appendSide + "Height", branchheight + add );
				} else {
					if ( parent === node ) {
						parent.setData( "branchheight", defaultHeight + 10 );
					} else {
						parent.setData( "branchheight", branchheight + add );
					}
				}
				parent = parent.getParent();
			} while ( parent || ( parent === root ) );
			if ( siblings.length === 1 ) {
				return false;
			} else {
				return true;
			}
		} else {
			var dec = node.getData( "branchheight" );
			do {
				var branchheight2 = parent.getData( appendSide + "Height" ) || parent.getData( "branchheight" ) || 0;
				if ( parent === root ) {
					parent.setData( appendSide + "Height", branchheight2 - dec );
				} else {
					parent.setData( "branchheight", branchheight2 - dec );
				}
				parent = parent.getParent();
			} while ( parent );
			return true;
		}
	};
	var reAnalyze = function ( km, layerArray, appendSide ) {
		for ( var lv = 0; lv < layerArray.length; lv++ ) {
			var lvData = layerArray[ lv ];
			for ( var i = 0; i < lvData.length; i++ ) {
				var children = ( lv === 0 ? layerArray[ 1 ] : lvData[ i ].getChildren() );
				if ( !children || children.length === 0 ) continue;
				var branchheight = lvData[ i ].getData( appendSide + "Height" ) || lvData[ i ].getData( "branchheight" );
				var sY = lvData[ i ].getData( "y" ) + ( children[ 0 ].getData( "branchheight" ) - branchheight ) / 2;
				for ( var j = 0; j < children.length; j++ ) {
					children[ j ].setData( "y", sY );
					var part1 = ( children[ j ].getData( "branchheight" ) - 10 ) / 2 + 10;
					var part2 = ( children[ j + 1 ] ? ( children[ j + 1 ].getData( "branchheight" ) - 10 ) / 2 : 0 );
					sY += ( part1 + part2 );
				}
				km.execCommand( "rendernode", children );
			}
		}
	};
	var createChildNode = function ( km, parent, index ) {
		var root = km.getRoot();
		var appendSide = parent.getData( "appendside" );
		var _node = new MinderNode();
		_node.setData( "branchheight", 0 );
		parent.insertChild( _node, index );

		_node.setData( "appendside", appendSide );
		if ( parent === root ) {
			var sidecount = root.getData( "sidecount" );
			if ( sidecount + 1 === 5 ) {
				root.setData( "sidecount", 0 );
				root.setData( "appendside", ( "leftright" ).replace( appendSide, "" ) );
			} else {
				root.setData( "sidecount", sidecount + 1 );
			}
		}
		var parentX = parent.getData( "x" );
		var parentWidth = parent.getRenderContainer().getWidth();

		switch ( appendSide ) {
		case "left":
			_node.setData( "x", parentX - parentWidth - 50 );
			_node.setData( "align", "right" );
			break;
		case "right":
			_node.setData( "x", parentX + parentWidth + 50 );
			_node.setData( "align", "left" );
			break;
		default:
			break;
		}

		var layer = parent.getData( "layer" ) + 1;
		var layerArray = root.getData( "layer" + appendSide );
		layerArray[ layer ] = layerArray[ layer ] || [];
		var layerData = layerArray[ layer ];
		var insertPos = 0;
		_node.setData( "layer", layer );

		//遍历层级链
		var getIndexList = function ( node ) {
			var indexList = [];
			var parent = node;
			do {
				indexList.push( parent.getIndex() );
				parent = parent.getParent();
			} while ( parent );
			return indexList.reverse();
		};

		//比较两个层级链的大小
		var indexLarger = function ( List1, List2 ) {
			var larger = true;
			for ( var i = 0; i < List1.length; i++ ) {
				if ( List1[ i ] == List2[ i ] ) {
					continue;
				}
				if ( List1[ i ] < List2[ i ] ) {
					larger = false;
				}
				break;
			}
			return larger;
		};

		//选定合适的位置插入节点
		for ( var l = layerData.length - 1; l >= 0; l-- ) {
			var nodeIndexList = getIndexList( _node );
			if ( !indexLarger( getIndexList( layerData[ l ] ), nodeIndexList ) ) {
				insertPos = l + 1;
				break;
			}
		}

		layerData.splice( insertPos, 0, _node );
		var reAnal = updateBranchHeight( _node, appendSide, root, true );
		//判断是重绘全部还是只是添加节点
		if ( reAnal ) {
			reAnalyze( km, layerArray, appendSide );
		} else {
			_node.setData( "y", _node.getParent().getData( "y" ) );
			km.execCommand( "rendernode", _node );
		}
		return _node;
	};

	var CreateChildNodeCommand = kity.createClass( "CreateChildNodeCommand", ( function () {
		return {
			base: Command,
			execute: createChildNode
		};
	} )() );

	var CreateSiblingNodeCommand = kity.createClass( "CreateSiblingNodeCommand", ( function () {
		return {
			base: Command,
			execute: function ( km, sibling ) {
				var parent = sibling.getParent();
				var index = sibling.getIndex() + 1;
				if ( parent ) {
					return createChildNode( km, parent, index );
				} else {
					return false;
				}
			}
		};
	} )() );

	var RemoveNodeCommand = kity.createClass( "RemoveNodeCommand", ( function () {

		return {
			base: Command,
			execute: function ( km, nodes ) {
				var root = km.getRoot();
				for ( var i = 0; i < nodes.length; i++ ) {
					var parent = nodes[ i ].getParent();
					if ( parent ) {
						var appendSide = nodes[ i ].getData( "appendside" );
						var layer = nodes[ i ].getData( "layer" );
						parent.removeChild( nodes[ i ] );
						var layerArray = root.getData( "layer" + appendSide );
						var layerData = layerArray[ layer ];
						//移除层结构中的node
						for ( var j = 0; j < layerData.length; j++ ) {
							if ( layerData[ j ] === nodes[ i ] ) {
								layerData.splice( j, 1 );
								break;
							}
						}
						var reAnal = updateBranchHeight( nodes[ i ], appendSide, root, false, parent );
						console.log( layerArray );
						if ( reAnal ) {
							reAnalyze( km, layerArray, appendSide );
						}
					}
				}
				this.setContentChanged( true );
			}
		};
	} )() );
	return {
		"commands": {
			"createchildnode": CreateChildNodeCommand,
			"createsiblingnode": CreateSiblingNodeCommand,
			"removenode": RemoveNodeCommand
		},

		"events": {

		}
	};
} );<|endoftext|>


<commit_before>const moment = require("moment");
const Eris = require("eris");
const utils = require("../utils");
const threads = require("../data/threads");
const blocked = require("../data/blocked");
const { messageQueue } = require("../queue");
const { getLogUrl, getLogFile, getLogCustomResponse } = require("../data/logs");

module.exports = ({ bot, knex, config, commands }) => {
  async function sendCloseNotification(thread, body) {
    const logCustomResponse = await getLogCustomResponse(thread);
    if (logCustomResponse) {
      await utils.postLog(body);
      await utils.postLog(logCustomResponse.content, logCustomResponse.file);
      return;
    }

    const logUrl = await getLogUrl(thread);
    if (logUrl) {
      utils.postLog(utils.trimAll(`
          ${body}
          Logs: ${logUrl}
        `));
      return;
    }

    const logFile = await getLogFile(thread);
    if (logFile) {
      utils.postLog(body, logFile);
      return;
    }

    utils.postLog(body);
  }

  // Check for threads that are scheduled to be closed and close them
  async function applyScheduledCloses() {
    const threadsToBeClosed = await threads.getThreadsThatShouldBeClosed();
    for (const thread of threadsToBeClosed) {
      if (config.closeMessage && ! thread.scheduled_close_silent) {
        const closeMessage = utils.readMultilineConfigValue(config.closeMessage);
        await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
      }

      await thread.close(false, thread.scheduled_close_silent);

      await sendCloseNotification(thread, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed as scheduled by ${thread.scheduled_close_name}`);
    }
  }

  async function scheduledCloseLoop() {
    try {
      await applyScheduledCloses();
    } catch (e) {
      console.error(e);
    }

    setTimeout(scheduledCloseLoop, 2000);
  }

  scheduledCloseLoop();

  // Close a thread. Closing a thread saves a log of the channel's contents and then deletes the channel.
  commands.addGlobalCommand("close", "[opts...]", async (msg, args) => {
    let thread, closedBy;

    let hasCloseMessage = !! config.closeMessage;
    let silentClose = false;

    if (msg.channel instanceof Eris.PrivateChannel) {
      // User is closing the thread by themselves (if enabled)
      if (! config.allowUserClose) return;
      if (await blocked.isBlocked(msg.author.id)) return;

      thread = await threads.findOpenThreadByUserId(msg.author.id);
      if (! thread) return;

      // We need to add this operation to the message queue so we don't get a race condition
      // between showing the close command in the thread and closing the thread
      await messageQueue.add(async () => {
        thread.postSystemMessage("Thread closed by user, closing...");
        await thread.close(true);
      });

      closedBy = "the user";
    } else {
      // A staff member is closing the thread
      if (! utils.messageIsOnInboxServer(msg)) return;
      if (! utils.isStaff(msg.member)) return;

      thread = await threads.findOpenThreadByChannelId(msg.channel.id);
      if (! thread) return;

      if (args.opts && args.opts.length) {
        if (args.opts.includes("cancel") || args.opts.includes("c")) {
          // Cancel timed close
          if (thread.scheduled_close_at) {
            await thread.cancelScheduledClose();
            thread.postSystemMessage("Cancelled scheduled closing");
          }

          return;
        }

        // Silent close (= no close message)
        if (args.opts.includes("silent") || args.opts.includes("s")) {
          silentClose = true;
        }

        // Timed close
        const delayStringArg = args.opts.find(arg => utils.delayStringRegex.test(arg));
        if (delayStringArg) {
          const delay = utils.convertDelayStringToMS(delayStringArg);
          if (delay === 0 || delay === null) {
            thread.postSystemMessage("Invalid delay specified. Format: \"1h30m\"");
            return;
          }

          const closeAt = moment.utc().add(delay, "ms");
          await thread.scheduleClose(closeAt.format("YYYY-MM-DD HH:mm:ss"), msg.author, silentClose ? 1 : 0);

          let response;
          if (silentClose) {
            response = `Thread is now scheduled to be closed silently in ${utils.humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`;
          } else {
            response = `Thread is now scheduled to be closed in ${utils.humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`;
          }

          thread.postSystemMessage(response);

          return;
        }
      }

      // Regular close
      await thread.close(false, silentClose);
      closedBy = msg.author.username;
    }

    // Send close message (unless suppressed with a silent close)
    if (hasCloseMessage && ! silentClose) {
      const closeMessage = utils.readMultilineConfigValue(config.closeMessage);
      await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
    }

    await sendCloseNotification(thread, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${closedBy}`);
  });

  // Auto-close threads if their channel is deleted
  bot.on("channelDelete", async (channel) => {
    if (! (channel instanceof Eris.TextChannel)) return;
    if (channel.guild.id !== utils.getInboxGuild().id) return;

    const thread = await threads.findOpenThreadByChannelId(channel.id);
    if (! thread) return;

    console.log(`[INFO] Auto-closing thread with ${thread.user_name} because the channel was deleted`);
    if (config.closeMessage) {
      const closeMessage = utils.readMultilineConfigValue(config.closeMessage);
      await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
    }

    await thread.close(true);

    await sendCloseNotification(thread, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed automatically because the channel was deleted`);
  });
};
<commit_msg>Fix thread channel being deleted before the close message is sent<commit_after>const moment = require("moment");
const Eris = require("eris");
const utils = require("../utils");
const threads = require("../data/threads");
const blocked = require("../data/blocked");
const { messageQueue } = require("../queue");
const { getLogUrl, getLogFile, getLogCustomResponse } = require("../data/logs");

module.exports = ({ bot, knex, config, commands }) => {
  async function sendCloseNotification(thread, body) {
    const logCustomResponse = await getLogCustomResponse(thread);
    if (logCustomResponse) {
      await utils.postLog(body);
      await utils.postLog(logCustomResponse.content, logCustomResponse.file);
      return;
    }

    const logUrl = await getLogUrl(thread);
    if (logUrl) {
      utils.postLog(utils.trimAll(`
          ${body}
          Logs: ${logUrl}
        `));
      return;
    }

    const logFile = await getLogFile(thread);
    if (logFile) {
      utils.postLog(body, logFile);
      return;
    }

    utils.postLog(body);
  }

  // Check for threads that are scheduled to be closed and close them
  async function applyScheduledCloses() {
    const threadsToBeClosed = await threads.getThreadsThatShouldBeClosed();
    for (const thread of threadsToBeClosed) {
      if (config.closeMessage && ! thread.scheduled_close_silent) {
        const closeMessage = utils.readMultilineConfigValue(config.closeMessage);
        await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
      }

      await thread.close(false, thread.scheduled_close_silent);

      await sendCloseNotification(thread, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed as scheduled by ${thread.scheduled_close_name}`);
    }
  }

  async function scheduledCloseLoop() {
    try {
      await applyScheduledCloses();
    } catch (e) {
      console.error(e);
    }

    setTimeout(scheduledCloseLoop, 2000);
  }

  scheduledCloseLoop();

  // Close a thread. Closing a thread saves a log of the channel's contents and then deletes the channel.
  commands.addGlobalCommand("close", "[opts...]", async (msg, args) => {
    let thread, closedBy;

    let hasCloseMessage = !! config.closeMessage;
    let silentClose = false;
    let suppressSystemMessages = false;

    if (msg.channel instanceof Eris.PrivateChannel) {
      // User is closing the thread by themselves (if enabled)
      if (! config.allowUserClose) return;
      if (await blocked.isBlocked(msg.author.id)) return;

      thread = await threads.findOpenThreadByUserId(msg.author.id);
      if (! thread) return;

      // We need to add this operation to the message queue so we don't get a race condition
      // between showing the close command in the thread and closing the thread
      await messageQueue.add(async () => {
        thread.postSystemMessage("Thread closed by user, closing...");
        suppressSystemMessages = true;
      });

      closedBy = "the user";
    } else {
      // A staff member is closing the thread
      if (! utils.messageIsOnInboxServer(msg)) return;
      if (! utils.isStaff(msg.member)) return;

      thread = await threads.findOpenThreadByChannelId(msg.channel.id);
      if (! thread) return;

      if (args.opts && args.opts.length) {
        if (args.opts.includes("cancel") || args.opts.includes("c")) {
          // Cancel timed close
          if (thread.scheduled_close_at) {
            await thread.cancelScheduledClose();
            thread.postSystemMessage("Cancelled scheduled closing");
          }

          return;
        }

        // Silent close (= no close message)
        if (args.opts.includes("silent") || args.opts.includes("s")) {
          silentClose = true;
        }

        // Timed close
        const delayStringArg = args.opts.find(arg => utils.delayStringRegex.test(arg));
        if (delayStringArg) {
          const delay = utils.convertDelayStringToMS(delayStringArg);
          if (delay === 0 || delay === null) {
            thread.postSystemMessage("Invalid delay specified. Format: \"1h30m\"");
            return;
          }

          const closeAt = moment.utc().add(delay, "ms");
          await thread.scheduleClose(closeAt.format("YYYY-MM-DD HH:mm:ss"), msg.author, silentClose ? 1 : 0);

          let response;
          if (silentClose) {
            response = `Thread is now scheduled to be closed silently in ${utils.humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`;
          } else {
            response = `Thread is now scheduled to be closed in ${utils.humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`;
          }

          thread.postSystemMessage(response);

          return;
        }
      }

      // Regular close
      closedBy = msg.author.username;
    }

    // Send close message (unless suppressed with a silent close)
    if (hasCloseMessage && ! silentClose) {
      const closeMessage = utils.readMultilineConfigValue(config.closeMessage);
      await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
    }

    await thread.close(suppressSystemMessages, silentClose);

    await sendCloseNotification(thread, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${closedBy}`);
  });

  // Auto-close threads if their channel is deleted
  bot.on("channelDelete", async (channel) => {
    if (! (channel instanceof Eris.TextChannel)) return;
    if (channel.guild.id !== utils.getInboxGuild().id) return;

    const thread = await threads.findOpenThreadByChannelId(channel.id);
    if (! thread) return;

    console.log(`[INFO] Auto-closing thread with ${thread.user_name} because the channel was deleted`);
    if (config.closeMessage) {
      const closeMessage = utils.readMultilineConfigValue(config.closeMessage);
      await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
    }

    await thread.close(true);

    await sendCloseNotification(thread, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed automatically because the channel was deleted`);
  });
};
<|endoftext|>


<commit_before>// Polyfill for startsWith.
if (!String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: function(searchString, position) {
            position = position || 0;
            return this.lastIndexOf(searchString, position) === position;
        }
    });
}

/**
 * Parse a string representing a map in the MovingAI format.
 * @param map_string The input string.
 * @returns {{}} The JSON object representation of the map.
 */
function parseMapString (map_string) {
    // Parse a single command in the map header.
    var parseCommand = (command_string) => {
        var splitted = command_string.split(" ");
        return Object.freeze({name: splitted[0], value: splitted[1]});
    };

    var json_map = {};

    var themap_splitted = map_string.split("\n");
    var i = 0;
    var command = themap_splitted[i].trim();
    // Parse Header
    while (i < themap_splitted.length && !command.startsWith("map")) {
        if (command.startsWith("height")) {
            json_map.height = parseInt(parseCommand(command).value, 10);
        } else if (command.startsWith("width")) {
            json_map.width = parseInt(parseCommand(command).value, 10);
        } else if (command.startsWith("type")) {
            json_map.type = parseCommand(command).value;
        }
        command = themap_splitted[i].trim();
        i++;
    }

    // Parse Map
    json_map.matrix = new Array(json_map.height);
    for (var r=0;r<json_map.height;r++) {
        json_map.matrix[r] = new Array(json_map.width);
        for (var c=0;c<json_map.width;c++) {
            json_map.matrix[r][c] = themap_splitted[r+i][c];
        }
    }
    return json_map;
}

/**
 * Parse a string representing scenario benchmark in the MovingAI format.
 * @param scenString The input string.
 * @returns {Array} The JSON representation of the input string scenario.
 */
function parseScenString(scenString) {
    let scenarios = scenString.split("\n").slice(1); // Split and remove the header.

    let parseScenario = (scenarioString) => {
        const parsed = scenarioString.split('\t');
        return {
            bucket: parsed[0],
            map: parsed[1],
            mapWidth: parsed[2],
            mapHeight: parsed[3],
            startX: parsed[4],
            startY: parsed[5],
            goalX: parsed[6],
            goalY: parsed[7],
            optimalLength: parsed[8]
        };
    };

    var scensJSON = [];
    for (let i=0; i<scenarios.length; i++) {
        if (scenarios[i].length < 5) { continue; }// Skip empty lines.
        scensJSON.push(parseScenario(scenarios[i]));
    }
    return scensJSON;
}

/******************************************************************************
 * FILE OPERATIONS
 *
 */

var fs = require('fs');
var async = require('async');

/**
 * Get a .map file and produce a file .map.json with the JSON representation of
 * the map.
 * @param filePath The input file path.
 * @param onComplete Callback executed when the output file is written.
 */
function parseMapFile(filePath, onComplete = () => {}) {
    fs.readFile( filePath, (err, data) => {
        if (err) {
            throw err;
        }
        const outJSON = parseMapString(data.toString());
        fs.writeFile(filePath + ".json", JSON.stringify(outJSON), (err) => {
            if (err) {
                return console.log(err);
            }
            onComplete();
        });
    });
}

function parseScenFile(filePath, onComplete = () => {}) {
    fs.readFile( filePath, (err, data) => {
        if (err) {
           throw err;
        }
        const outJSON = parseScenString(data.toString());
        fs.writeFile(filePath + ".json", JSON.stringify(outJSON), (err) => {
            if (err) {
                return console.log(err);
            }
            onComplete();
        });
    });
}

/**
 * Parse all the files in the given folder.
 * @param folder The input folder path.
 * @param onComplete A callback executed when all the files are processed.
 */
function parseAllInFolder(folder, onComplete = () => {}) {
    let parseFile = (file, callback) => {
        if (file.substr(-4) === ".map") {
            parseMapFile(file, callback);
        } else if (file.substr(-5) === ".scen") {
            parseScenFile(file, callback);
        }
    };

    fs.readdir(folder, (err, files) =>  {
        files = files.filter( (file) => file.substr(-4) === ".map" || file.substr(-5) === ".scen");
        // console.log(`${files.length} files found!`);
        async.each(files, (file, callback) => parseFile(folder + "/" + file, callback), (err) => {
            if (err) {
                throw err;
            }
            onComplete();
        });
    });
}

exports.parseMapString = parseMapString;
exports.parseScenString = parseScenString;
exports.parseMapFile = parseMapFile;
exports.parseAllInFolder = parseAllInFolder;

/******************************************************************************
 * COMMAND LINE INTERFACE
 *
 */

function printUsage(programName) {
    console.log("Usage:");
    console.log(`${programName} batch [folder] -- Convert all the .map file in the folder path.` );
}

if (module.parent === undefined) {
    let myArgs = process.argv.slice(2);

    if (myArgs.length < 2) {
        printUsage(process.argv[0] + " " + process.argv[1]);
    } else {
        if (myArgs[0] === "batch") {
            console.log("Run batch conversion");
            parseAllInFolder(myArgs[1], () => console.log("Completed!"));
        }
    }
}

<commit_msg>Fixed a bug in the CLI insterface.<commit_after>// Polyfill for startsWith.
if (!String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: function(searchString, position) {
            position = position || 0;
            return this.lastIndexOf(searchString, position) === position;
        }
    });
}

/**
 * Parse a string representing a map in the MovingAI format.
 * @param map_string The input string.
 * @returns {{}} The JSON object representation of the map.
 */
function parseMapString (map_string) {
    // Parse a single command in the map header.
    var parseCommand = (command_string) => {
        var splitted = command_string.split(" ");
        return Object.freeze({name: splitted[0], value: splitted[1]});
    };

    var json_map = {};

    var themap_splitted = map_string.split("\n");
    var i = 0;
    var command = themap_splitted[i].trim();
    // Parse Header
    while (i < themap_splitted.length && !command.startsWith("map")) {
        if (command.startsWith("height")) {
            json_map.height = parseInt(parseCommand(command).value, 10);
        } else if (command.startsWith("width")) {
            json_map.width = parseInt(parseCommand(command).value, 10);
        } else if (command.startsWith("type")) {
            json_map.type = parseCommand(command).value;
        }
        command = themap_splitted[i].trim();
        i++;
    }

    // Parse Map
    json_map.matrix = new Array(json_map.height);
    for (var r=0;r<json_map.height;r++) {
        json_map.matrix[r] = new Array(json_map.width);
        for (var c=0;c<json_map.width;c++) {
            json_map.matrix[r][c] = themap_splitted[r+i][c];
        }
    }
    return json_map;
}

/**
 * Parse a string representing scenario benchmark in the MovingAI format.
 * @param scenString The input string.
 * @returns {Array} The JSON representation of the input string scenario.
 */
function parseScenString(scenString) {
    let scenarios = scenString.split("\n").slice(1); // Split and remove the header.

    let parseScenario = (scenarioString) => {
        const parsed = scenarioString.split('\t');
        return {
            bucket: parsed[0],
            map: parsed[1],
            mapWidth: parsed[2],
            mapHeight: parsed[3],
            startX: parsed[4],
            startY: parsed[5],
            goalX: parsed[6],
            goalY: parsed[7],
            optimalLength: parsed[8]
        };
    };

    var scensJSON = [];
    for (let i=0; i<scenarios.length; i++) {
        if (scenarios[i].length < 5) { continue; }// Skip empty lines.
        scensJSON.push(parseScenario(scenarios[i]));
    }
    return scensJSON;
}

/******************************************************************************
 * FILE OPERATIONS
 *
 */

var fs = require('fs');
var async = require('async');

/**
 * Get a .map file and produce a file .map.json with the JSON representation of
 * the map.
 * @param filePath The input file path.
 * @param onComplete Callback executed when the output file is written.
 */
function parseMapFile(filePath, onComplete = () => {}) {
    fs.readFile( filePath, (err, data) => {
        if (err) {
            throw err;
        }
        const outJSON = parseMapString(data.toString());
        fs.writeFile(filePath + ".json", JSON.stringify(outJSON), (err) => {
            if (err) {
                return console.log(err);
            }
            onComplete();
        });
    });
}

/**
 * Get a .scen file and produce a file .scen.json with the JSON representation of the
 * scen file contents.
 * @param filePath The input file path.
 * @param onComplete Callback executed when the output file is written.
 */
function parseScenFile(filePath, onComplete = () => {}) {
    fs.readFile( filePath, (err, data) => {
        if (err) {
           throw err;
        }
        const outJSON = parseScenString(data.toString());
        fs.writeFile(filePath + ".json", JSON.stringify(outJSON), (err) => {
            if (err) {
                return console.log(err);
            }
            onComplete();
        });
    });
}

/**
 * Parse all the files in the given folder.
 * @param folder The input folder path.
 * @param onComplete A callback executed when all the files are processed.
 */
function parseAllInFolder(folder, onComplete = () => {}) {
    let parseFile = (file, callback) => {
        if (file.substr(-4) === ".map") {
            parseMapFile(file, callback);
        } else if (file.substr(-5) === ".scen") {
            parseScenFile(file, callback);
        }
    };

    fs.readdir(folder, (err, files) =>  {
        files = files.filter( (file) => file.substr(-4) === ".map" || file.substr(-5) === ".scen");
        // console.log(`${files.length} files found!`);
        async.each(files, (file, callback) => parseFile(folder + "/" + file, callback), (err) => {
            if (err) {
                throw err;
            }
            onComplete();
        });
    });
}

exports.parseMapString = parseMapString;
exports.parseScenString = parseScenString;
exports.parseMapFile = parseMapFile;
exports.parseAllInFolder = parseAllInFolder;

/******************************************************************************
 * COMMAND LINE INTERFACE
 *
 */

function printUsage(programName) {
    console.log("Usage:");
    console.log(`${programName} batch [folder] -- Convert all the .map file in the folder path.` );
}

if (module.parent === undefined || module.parent === null) {
    let myArgs = process.argv.slice(2);

    if (myArgs.length < 2) {
        printUsage(process.argv[0] + " " + process.argv[1]);
    } else {
        if (myArgs[0] === "batch") {
            console.log("Run batch conversion");
            parseAllInFolder(myArgs[1], () => console.log("Completed!"));
        }
    }
}

<|endoftext|>


<commit_before>"use strict";
require("requirish")._(module);
// produce nodeset xml files
var XMLWriter = require("xml-writer");

var UAVariable = require("./ua_variable").UAVariable;
var BaseNode = require("./base_node").BaseNode;
var UADataType = require("./ua_data_type").UADataType;
var UAObject = require("./ua_object").UAObject;
var UAObjectType = require("./ua_object_type").UAObjectType;
var UAMethod = require("./ua_method").UAMethod;
var VariantArrayType = require("lib/datamodel/variant").VariantArrayType;
var NodeId = require("lib/datamodel/nodeid").NodeId;
var Variant = require("lib/datamodel/variant").Variant;
var assert = require("better-assert");
var _ = require("underscore");

function _dumpDisplayName(xw,node) {
    xw.startElement("DisplayName").text(node.displayName[0].text).endElement();
}
function _dumpDescription(xw,node) {
    if (node.description) {
        var desc = node.description.text;
        desc = desc || "";
        xw.startElement("Description").text(desc).endElement();
    }
}

function _dumpReferences(xw,node) {
    xw.startElement("References");
    node._references.forEach(function(reference){
        xw.startElement("Reference");
            xw.writeAttribute("ReferenceType",reference.referenceType);
            if (!reference.isForward) {
                xw.writeAttribute("IsForward",reference.isForward?"true":"false");
            }
            xw.text(reference.nodeId.toString());
        xw.endElement();
    });
    xw.endElement();

}

BaseNode.prototype.dumpXML = function(xmlWriter) {

    console.log(" This ",this.nodeClass);
    assert(false," NOT IMPLEMENTED !");
};

function _dumpVariantExtensionObjectValue_Body(xw,schema,value) {

    xw.startElement(schema.name);
    if (value)  {
        schema.fields.forEach(function(field){
            xw.startElement(field.name);
            var v = value[field.name];
            if(v!==null && v!==undefined) {
                //xx console.log("xxxxx field",field," V ".red,v);
                xw.text(v.toString());
            }
            xw.endElement();
        });
    }
    xw.endElement();

}
var getFactory = require("lib/misc/factories_factories").getFactory;
/* encode object as XML */
function _dumpVariantExtensionObjectValue(xw,schema,value) {

    xw.startElement("ExtensionObject");
    {
        xw.startElement("TypeId");
        {
            // find HasEncoding node
            var encodingDefaultXml = getFactory(schema.name).prototype.encodingDefaultXml;
            //xx var encodingDefaultXml = schema.encodingDefaultXml;
            xw.startElement("Identifier");
            xw.text(encodingDefaultXml.toString());
            xw.endElement();
        }
        xw.endElement();

        xw.startElement("Body");
        _dumpVariantExtensionObjectValue_Body(xw,schema,value);
        xw.endElement();
    }
    xw.endElement();
}
function _dumpValue(xw,node) {
    var address_space = node.__address_space;

    var value = node._dataValue.value;
    if (!value) {
        return;
    }
    //xx console.log(value);

    assert(value instanceof Variant);

    var dataTypeName = address_space.findObject(node.dataType).browseName.toString(); // value.dataType.toString();

    var f = getFactory(dataTypeName);
    if (!f)  {
        console.log("Cannot find ",dataTypeName);
        return;
    }
    var schema = f.prototype._schema;

    //xx console.log("xxxxxxxxx schema".cyan,dataTypeName.yellow,schema);
    function encodeXml(value) {
        _dumpVariantExtensionObjectValue_Body(xw,schema,value)
    }

    xw.startElement("Value");

    // determine if dataTypeName is a ExtensionObject
    var isExtensionObject = dataTypeName === "LocalizedText"? false : true;

    if (isExtensionObject) {

        if (value.arrayType === VariantArrayType.Array) {
            xw.startElement("ListOf"+dataTypeName);
            value.value.forEach(_dumpVariantExtensionObjectValue.bind(null,xw,schema));
            xw.endElement();
        } else if (value.arrayType === VariantArrayType.Scalar) {
            _dumpVariantExtensionObjectValue(xw,schema,value);
        }
    } else {

        if (value.arrayType === VariantArrayType.Array) {
            xw.startElement("ListOf"+dataTypeName);
            value.value.forEach(encodeXml);
            xw.endElement();
        } else if (value.arrayType === VariantArrayType.Scalar) {
            encodeXml(value.value);
        }
    }

    xw.endElement();
}

function dumpUAVariable(xw,node) {

    var address_space = node.__address_space;
    xw.startElement("UAVariable");
    xw.writeAttribute("NodeId",node.nodeId.toString());
    xw.writeAttribute("BrowseName",node.browseName.toString());
    xw.writeAttribute("SymbolicName",node.symbolicName);
    if (node.valueRank !== -1) {
        xw.writeAttribute("ValueRank",node.valueRank);
    }
    var dataTypeNode = address_space.findObject(node.dataType);
    if (!dataTypeNode) {
        throw new Error(" cannot find datatype " + node.dataType);
    }
    var dataTypeName = dataTypeNode.browseName.toString();
    xw.writeAttribute("DataType",dataTypeName);

    _dumpDisplayName(xw,node);
    _dumpDescription(xw,node);

    _dumpReferences(xw,node);

    _dumpValue(xw,node);
    xw.endElement();

}

UAVariable.prototype.dumpXML = function(xw) {
    dumpUAVariable(xw,this);
};

function _dumpUADataTypeDefinition(xw,node) {

    if (node.definition.length) {
        xw.startElement("Definition");
        xw.writeAttribute("Name",node.definitionName);

        node.definition.forEach(function(defItem){
            xw.startElement("Field");
            xw.writeAttribute("Name",    defItem.name);
            xw.writeAttribute("DataType",defItem.dataType);
            if (defItem.valueRank !== undefined) {
                xw.writeAttribute("ValueRank",defItem.valueRank);
            }
            if (defItem.value !== undefined) {
                xw.writeAttribute("Value",defItem.value);
            }
            if (defItem.description) {
                xw.startElement("Description");
                xw.text(defItem.description);
                xw.endElement();
            }
            xw.endElement();
        });
        xw.endElement();
    }
}

function dumpUADataType(xw,node) {
    xw.startElement("UADataType");
    xw.writeAttribute("NodeId",node.nodeId.toString());
    xw.writeAttribute("BrowseName",node.browseName.toString());

    if (node.isAbstract) {
        xw.writeAttribute("IsAbstract",node.isAbstract ? "true" :"false");
    }
    _dumpReferences(xw,node);
    _dumpUADataTypeDefinition(xw,node);

    xw.endElement();

}
UADataType.prototype.dumpXML = function(xw) {
    dumpUADataType(xw,this);
};

function dumpUAObject(xw,node) {

    xw.startElement("UAObject");
    xw.writeAttribute("NodeId",node.nodeId.toString());
    xw.writeAttribute("BrowseName",node.browseName.toString());
    xw.writeAttribute("SymbolicName",node.symbolicName);

    _dumpDisplayName(xw,node);
    _dumpDescription(xw,node);

    _dumpReferences(xw,node);
    xw.endElement();

}
UAObject.prototype.dumpXML = function(xw) {
    dumpUAObject(xw,this);
};

function dumpUAObjectType(xw,node) {

    xw.startElement("UAObjectType");
    xw.writeAttribute("NodeId",node.nodeId.toString());
    xw.writeAttribute("BrowseName",node.browseName.toString());
    xw.writeAttribute("IsAbstract",node.isAbstract ? "true" :"false");
    //xx xw.writeAttribute("SymbolicName",node.symbolicName);
    _dumpDisplayName(xw,node);
    _dumpDescription(xw,node);

    _dumpReferences(xw,node);
    xw.endElement();
}

UAObjectType.prototype.dumpXML = function(xw) {
    dumpUAObjectType(xw,this);
};

function dumpUAMethod(xw,node) {

    xw.startElement("UAObjectType");
    xw.writeAttribute("NodeId",node.nodeId.toString());
    xw.writeAttribute("BrowseName",node.browseName.toString());
    xw.writeAttribute("IsAbstract",node.isAbstract ? "true" :"false");
    //xx xw.writeAttribute("SymbolicName",node.symbolicName);
    _dumpDisplayName(xw,node);
    _dumpDescription(xw,node);

    _dumpReferences(xw,node);
    xw.endElement();
}
UAMethod.prototype.dumpXML = function(xw) {
    dumpUAMethod(xw,this);
};

function visitUANode(node,options)
{

    //xx console.log("xxxxx visiting ", node.nodeId.toString(),node.browseName);
    var address_space = node.__address_space;
    options.elements = options.elements || [];
    options.index_el = options.index_el || {};
    // visit references
    function process_reference(reference) {
        // reference.referenceType
        if (!reference.isForward) {
            return;
        }
        //xx console.log("xxxxx reference=".cyan,reference);
        if (reference.nodeId.namespace === 0) {
            return;// skip OPCUA namespace
        }
        var k = reference.nodeId.toString();
        if (!options.index_el[k]) {
            options.index_el[k]=1;

            var o = address_space.findObject(k);
            if (o) {
                visitUANode(o,options);
            }
        }
    }
    node._references.forEach(process_reference);
    node._back_references.forEach(process_reference);
    options.elements.push(node);
    return node;
}

function dumpXml(node,options) {

    var address_space = node.__address_space;

    // make a first visit so that we determine which node to output and in which order
    var s= {};

    function resolveDataTypeName(dataType) {
        if (_.isString(dataType)) {
            return address_space.findDataType(dataType);
        }
        assert(dataType instanceof NodeId);
        var o = address_space.findObject(dataType.toString());
         return o ? o.browseName.toString() : null;
    }

    function buildUpAliases(node,options) {

        options.aliases = options.aliases || {};
        options.aliases_visited =  options.aliases_visited  || {};

        var k = node.nodeId.toString();

        if (options.aliases_visited[k]) {
            return;
        }
        options.aliases_visited[k]=1;

        // put datatype into aliases list
        if (node.dataType && node.dataType.namespace ===0 ) {
            // name
            var dataTypeName =resolveDataTypeName(node.dataType);
            if (dataTypeName) {
                if (!options.aliases[dataTypeName]) {
                    options.aliases[dataTypeName] = node.dataType;
                }
            }
        }

        function add_in_aliases_map(key){
            if (!options.aliases.key) {
                var nodeId = address_space.resolveNodeId(key);
                if (nodeId.namespace === 0) {
                    options.aliases[key] = address_space.resolveNodeId(key);
                }

            }
        }
        function inner(reference) {
            // reference.referenceType
            var key = reference.referenceType;
            add_in_aliases_map(key);

            var o = address_space.findObject(reference.nodeId);
            if (o) {
                buildUpAliases(o,options);
            }
        }
        node._references.forEach(inner);
        node._back_references.forEach(inner);

    }
    function writeAliases(xw,aliases) {

        xw.startElement("Aliases");

        Object.keys(aliases).forEach(function(key) {
            xw.startElement("Alias");
            xw.writeAttribute("Alias",key);
            xw.text("i="+aliases[key].value.toString());
            xw.endElement();
        });

        xw.endElement();
    }

    buildUpAliases(node,s);

    var el = visitUANode(node,s);

    //xx console.log("xxxxx ",s.elements.map(function(e){ return e.nodeId.toString();}).join(" "));
    //xx s.elements.map(function(a){ console.log(a.nodeId.toString(), a.browseName); });

    var xw = new XMLWriter(true);
    xw.startDocument({encoding:"utf-8"});
    xw.startElement("UANodeSet");
        xw.writeAttribute("xmlns:xs","http://www.w3.org/2001/XMLSchema-instance");
        xw.writeAttribute("xmlns:xsd","http://www.w3.org/2001/XMLSchema");
        xw.writeAttribute("Version","1.02");
        xw.writeAttribute("LastModified",(new Date()).toISOString());
        xw.writeAttribute("xmlns","http://opcfoundation.org/UA/2011/03/UANodeSet.xsd");

        writeAliases(xw, s.aliases);

        s.elements.forEach(function(el){
            //xx console.log(el.nodeClass);
            el.dumpXML(xw);
        });

    xw.endElement();
    xw.endDocument();
    return xw.toString();
}

exports.dumpXml = dumpXml;<commit_msg>refactor nodeset_to_xml<commit_after>"use strict";
require("requirish")._(module);
// produce nodeset xml files
var XMLWriter = require("xml-writer");

var UAVariable = require("./ua_variable").UAVariable;
var BaseNode = require("./base_node").BaseNode;
var UADataType = require("./ua_data_type").UADataType;
var UAObject = require("./ua_object").UAObject;
var UAObjectType = require("./ua_object_type").UAObjectType;
var UAMethod = require("./ua_method").UAMethod;
var VariantArrayType = require("lib/datamodel/variant").VariantArrayType;
var NodeId = require("lib/datamodel/nodeid").NodeId;
var Variant = require("lib/datamodel/variant").Variant;
var assert = require("better-assert");
var _ = require("underscore");

function _dumpDisplayName(xw,node) {
    xw.startElement("DisplayName").text(node.displayName[0].text).endElement();
}
function _dumpDescription(xw,node) {
    if (node.description) {
        var desc = node.description.text;
        desc = desc || "";
        xw.startElement("Description").text(desc).endElement();
    }
}

function _dumpReferences(xw,node) {
    xw.startElement("References");
    node._references.forEach(function(reference){
        xw.startElement("Reference");
            xw.writeAttribute("ReferenceType",reference.referenceType);
            if (!reference.isForward) {
                xw.writeAttribute("IsForward",reference.isForward?"true":"false");
            }
            xw.text(reference.nodeId.toString());
        xw.endElement();
    });
    xw.endElement();

}

BaseNode.prototype.dumpXML = function(xmlWriter) {

    console.log(" This ",this.nodeClass);
    assert(false," NOT IMPLEMENTED !");
};

function _dumpVariantExtensionObjectValue_Body(xw,schema,value) {

    xw.startElement(schema.name);
    if (value)  {
        schema.fields.forEach(function(field){
            xw.startElement(field.name);
            var v = value[field.name];
            if(v!==null && v!==undefined) {
                //xx console.log("xxxxx field",field," V ".red,v);
                xw.text(v.toString());
            }
            xw.endElement();
        });
    }
    xw.endElement();

}
var getFactory = require("lib/misc/factories_factories").getFactory;
/* encode object as XML */
function _dumpVariantExtensionObjectValue(xw,schema,value) {

    xw.startElement("ExtensionObject");
    {
        xw.startElement("TypeId");
        {
            // find HasEncoding node
            var encodingDefaultXml = getFactory(schema.name).prototype.encodingDefaultXml;
            //xx var encodingDefaultXml = schema.encodingDefaultXml;
            xw.startElement("Identifier");
            xw.text(encodingDefaultXml.toString());
            xw.endElement();
        }
        xw.endElement();

        xw.startElement("Body");
        _dumpVariantExtensionObjectValue_Body(xw,schema,value);
        xw.endElement();
    }
    xw.endElement();
}
function _dumpValue(xw,node) {
    var address_space = node.__address_space;

    var value = node._dataValue.value;
    if (!value) {
        return;
    }
    //xx console.log(value);

    assert(value instanceof Variant);

    var dataTypeName = address_space.findObject(node.dataType).browseName.toString(); // value.dataType.toString();

    var f = getFactory(dataTypeName);
    if (!f)  {
        console.log("Cannot find ",dataTypeName);
        return;
    }
    var schema = f.prototype._schema;

    //xx console.log("xxxxxxxxx schema".cyan,dataTypeName.yellow,schema);
    function encodeXml(value) {
        _dumpVariantExtensionObjectValue_Body(xw,schema,value)
    }

    xw.startElement("Value");

    // determine if dataTypeName is a ExtensionObject
    var isExtensionObject = dataTypeName === "LocalizedText"? false : true;

    if (isExtensionObject) {

        if (value.arrayType === VariantArrayType.Array) {
            xw.startElement("ListOf"+dataTypeName);
            value.value.forEach(_dumpVariantExtensionObjectValue.bind(null,xw,schema));
            xw.endElement();
        } else if (value.arrayType === VariantArrayType.Scalar) {
            _dumpVariantExtensionObjectValue(xw,schema,value);
        }
    } else {

        if (value.arrayType === VariantArrayType.Array) {
            xw.startElement("ListOf"+dataTypeName);
            value.value.forEach(encodeXml);
            xw.endElement();
        } else if (value.arrayType === VariantArrayType.Scalar) {
            encodeXml(value.value);
        }
    }

    xw.endElement();
}

function dumpUAVariable(xw,node) {

    var address_space = node.__address_space;
    xw.startElement("UAVariable");
    xw.writeAttribute("NodeId",node.nodeId.toString());
    xw.writeAttribute("BrowseName",node.browseName.toString());
    xw.writeAttribute("SymbolicName",node.symbolicName);
    if (node.valueRank !== -1) {
        xw.writeAttribute("ValueRank",node.valueRank);
    }
    var dataTypeNode = address_space.findObject(node.dataType);
    if (!dataTypeNode) {
        throw new Error(" cannot find datatype " + node.dataType);
    }
    var dataTypeName = dataTypeNode.browseName.toString();
    xw.writeAttribute("DataType",dataTypeName);

    _dumpDisplayName(xw,node);
    _dumpDescription(xw,node);

    _dumpReferences(xw,node);

    _dumpValue(xw,node);
    xw.endElement();

}

UAVariable.prototype.dumpXML = function(xw) {
    dumpUAVariable(xw,this);
};

function _dumpUADataTypeDefinition(xw,node) {

    if (node.definition.length) {
        xw.startElement("Definition");
        xw.writeAttribute("Name",node.definitionName);

        node.definition.forEach(function(defItem){
            xw.startElement("Field");
            xw.writeAttribute("Name",    defItem.name);
            xw.writeAttribute("DataType",defItem.dataType);
            if (defItem.valueRank !== undefined) {
                xw.writeAttribute("ValueRank",defItem.valueRank);
            }
            if (defItem.value !== undefined) {
                xw.writeAttribute("Value",defItem.value);
            }
            if (defItem.description) {
                xw.startElement("Description");
                xw.text(defItem.description);
                xw.endElement();
            }
            xw.endElement();
        });
        xw.endElement();
    }
}

function dumpUADataType(xw,node) {
    xw.startElement("UADataType");
    xw.writeAttribute("NodeId",node.nodeId.toString());
    xw.writeAttribute("BrowseName",node.browseName.toString());

    if (node.isAbstract) {
        xw.writeAttribute("IsAbstract",node.isAbstract ? "true" :"false");
    }
    _dumpReferences(xw,node);
    _dumpUADataTypeDefinition(xw,node);

    xw.endElement();

}
UADataType.prototype.dumpXML = function(xw) {
    dumpUADataType(xw,this);
};
function dumpCommonAttributes(xw,node) {
    xw.writeAttribute("NodeId",node.nodeId.toString());
    xw.writeAttribute("BrowseName",node.browseName.toString());
    _dumpDisplayName(xw,node);
    _dumpDescription(xw,node);
    _dumpReferences(xw,node);

}
function dumpUAObject(xw,node) {

    xw.startElement("UAObject");
    xw.writeAttribute("SymbolicName",node.symbolicName);
    dumpCommonAttributes(xw,node);
    xw.endElement();

}
UAObject.prototype.dumpXML = function(xw) {
    dumpUAObject(xw,this);
};

function dumpUAObjectType(xw,node) {

    xw.startElement("UAObjectType");
    xw.writeAttribute("IsAbstract",node.isAbstract ? "true" :"false");
    dumpCommonAttributes(xw,node);
    xw.endElement();
}

UAObjectType.prototype.dumpXML = function(xw) {
    dumpUAObjectType(xw,this);
};

function dumpUAMethod(xw,node) {

    xw.startElement("UAMethod");
    xw.writeAttribute("IsAbstract",node.isAbstract ? "true" :"false");
    dumpCommonAttributes(xw,node);
    xw.endElement();
}
UAMethod.prototype.dumpXML = function(xw) {
    dumpUAMethod(xw,this);
};

function visitUANode(node,options)
{

    //xx console.log("xxxxx visiting ", node.nodeId.toString(),node.browseName);
    var address_space = node.__address_space;
    options.elements = options.elements || [];
    options.index_el = options.index_el || {};
    // visit references
    function process_reference(reference) {
        // reference.referenceType
        if (!reference.isForward) {
            return;
        }
        //xx console.log("xxxxx reference=".cyan,reference);
        if (reference.nodeId.namespace === 0) {
            return;// skip OPCUA namespace
        }
        var k = reference.nodeId.toString();
        if (!options.index_el[k]) {
            options.index_el[k]=1;

            var o = address_space.findObject(k);
            if (o) {
                visitUANode(o,options);
            }
        }
    }
    node._references.forEach(process_reference);
    node._back_references.forEach(process_reference);
    options.elements.push(node);
    return node;
}

function dumpXml(node,options) {

    var address_space = node.__address_space;

    // make a first visit so that we determine which node to output and in which order
    var s= {};

    function resolveDataTypeName(dataType) {
        if (_.isString(dataType)) {
            return address_space.findDataType(dataType);
        }
        assert(dataType instanceof NodeId);
        var o = address_space.findObject(dataType.toString());
         return o ? o.browseName.toString() : null;
    }

    function buildUpAliases(node,options) {

        options.aliases = options.aliases || {};
        options.aliases_visited =  options.aliases_visited  || {};

        var k = node.nodeId.toString();

        if (options.aliases_visited[k]) {
            return;
        }
        options.aliases_visited[k]=1;

        // put datatype into aliases list
        if (node.dataType && node.dataType.namespace ===0 ) {
            // name
            var dataTypeName =resolveDataTypeName(node.dataType);
            if (dataTypeName) {
                if (!options.aliases[dataTypeName]) {
                    options.aliases[dataTypeName] = node.dataType;
                }
            }
        }

        function add_in_aliases_map(key){
            if (!options.aliases.key) {
                var nodeId = address_space.resolveNodeId(key);
                if (nodeId.namespace === 0) {
                    options.aliases[key] = address_space.resolveNodeId(key);
                }

            }
        }
        function inner(reference) {
            // reference.referenceType
            var key = reference.referenceType;
            add_in_aliases_map(key);

            var o = address_space.findObject(reference.nodeId);
            if (o) {
                buildUpAliases(o,options);
            }
        }
        node._references.forEach(inner);
        node._back_references.forEach(inner);

    }
    function writeAliases(xw,aliases) {

        xw.startElement("Aliases");

        Object.keys(aliases).forEach(function(key) {
            xw.startElement("Alias");
            xw.writeAttribute("Alias",key);
            xw.text("i="+aliases[key].value.toString());
            xw.endElement();
        });

        xw.endElement();
    }

    buildUpAliases(node,s);

    var el = visitUANode(node,s);

    //xx console.log("xxxxx ",s.elements.map(function(e){ return e.nodeId.toString();}).join(" "));
    //xx s.elements.map(function(a){ console.log(a.nodeId.toString(), a.browseName); });

    var xw = new XMLWriter(true);
    xw.startDocument({encoding:"utf-8"});
    xw.startElement("UANodeSet");
        xw.writeAttribute("xmlns:xs","http://www.w3.org/2001/XMLSchema-instance");
        xw.writeAttribute("xmlns:xsd","http://www.w3.org/2001/XMLSchema");
        xw.writeAttribute("Version","1.02");
        xw.writeAttribute("LastModified",(new Date()).toISOString());
        xw.writeAttribute("xmlns","http://opcfoundation.org/UA/2011/03/UANodeSet.xsd");

        writeAliases(xw, s.aliases);

        s.elements.forEach(function(el){
            //xx console.log(el.nodeClass);
            el.dumpXML(xw);
        });

    xw.endElement();
    xw.endDocument();
    return xw.toString();
}

exports.dumpXml = dumpXml;<|endoftext|>


<commit_before>var path = require('path');
var nroonga = require('../../wrapped-nroonga');
var Domain = require('../../database').Domain;
var Translator = require('../../batch/translator').Translator;
var dateFormat = require('dateformat');
var xmlbuilder = require('../../xmlbuilder');

exports.version = path.basename(__dirname);

var XMLNS = 'http://cloudsearch.amazonaws.com/doc/2011-02-01';

function createCommonErrorResponse(errorCode, message) {
  return '<?xml version="1.0"?>\n' +
         '<Response>' +
           '<Errors>' +
             '<Error><Code>' + errorCode + '</Code>' +
                     '<Message>' + message + '</Message></Error>' +
             '</Errors>' +
           '<RequestID></RequestID>' +
         '</Response>';
}

var handlers = Object.create(null);

function getBaseDomain(domain) {
  return domain.replace(/^cloudsearch\./, '');
}

function createDomainStatus(options) {
  return '<DomainStatus>' +
           '<Created>' + (options.created || 'false') + '</Created>' +
           '<Deleted>' + (options.deleted || 'false') + '</Deleted>' +
           '<DocService>' +
             '<Endpoint>' + (options.documentsEndpoint || '') + '</Endpoint>' +
           '</DocService>' +
           '<DomainId>' + options.domainId + '/' + options.domainName +
             '</DomainId>' +
           '<DomainName>' + options.domainName + '</DomainName>' +
           '<NumSearchableDocs>' + (options.searchableDocumentsCount || 0) +
             '</NumSearchableDocs>' +
           '<RequiresIndexDocuments>' +
             (options.requiresIndexDocuments || 'false') +
             '</RequiresIndexDocuments>' +
           '<SearchInstanceCount>' + (options.searchInstanceCount || 0) +
             '</SearchInstanceCount>' +
           '<SearchPartitionCount>' + (options.searchPartitionCount || 0) +
             '</SearchPartitionCount>' +
           '<SearchService>' +
             '<Endpoint>' + (options.searchEndpoint || '') + '</Endpoint>' +
           '</SearchService>' +
         '</DomainStatus>';
}

function createCreateDomainResponse(options) {
  return '<?xml version="1.0"?>\n' +
         '<CreateDomainResponse xmlns="' + XMLNS + '">' +
           '<CreateDomainResult>' +
             createDomainStatus(options) +
           '</CreateDomainResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</CreateDomainResponse>';
}

handlers.CreateDomain = function(context, request, response) {
  var domain = new Domain(request, context);
  try {
    domain.createSync();
    var host = getBaseDomain(request.headers.host);
    host = host ? '.' + host : '';
    response.contentType('application/xml');
    response.send(createCreateDomainResponse({
      domainName: domain.name,
      domainId: domain.id,
      searchEndpoint: 'search-' + domain.name + '-' + domain.id + host,
      documentsEndpoint: 'doc-' + domain.name + '-' + domain.id + host,
      created: true,
      searchableDocumentsCount: 0,
      searchInstanceCount: 0,
      searchPartitionCount: 0,
      requiresIndexDocuments: false
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function createDeleteDomainResponse(options) {
  return '<?xml version="1.0"?>\n' +
         '<DeleteDomainResponse xmlns="' + XMLNS + '">' +
           '<DeleteDomainResult>' +
             createDomainStatus(options) +
           '</DeleteDomainResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</DeleteDomainResponse>';
}

handlers.DeleteDomain = function(context, request, response) {
  var domain = new Domain(request, context);
  try {
    domain.deleteSync();
    var host = getBaseDomain(request.headers.host);
    host = host ? '.' + host : '';
    response.contentType('application/xml');
    response.send(createDeleteDomainResponse({
      domainName: domain.name,
      domainId: domain.id,
      searchEndpoint: 'search-' + domain.name + '-' + domain.id + host,
      documentsEndpoint: 'doc-' + domain.name + '-' + domain.id + host,
      deleted: true,
      searchableDocumentsCount: 0,
      searchInstanceCount: 0,
      searchPartitionCount: 0,
      requiresIndexDocuments: false
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function createIndexFieldOptionStatus(options) {
  switch (options.fieldType) {
    case 'text':
      return '<TextOptions>' +
               '<DefaultValue/>' +
               '<FacetEnabled>' + (options.facetEnabled || false) +
                 '</FacetEnabled>' +
               '<ResultEnabled>' + (options.resultEnabled || false) +
                 '</ResultEnabled>' +
             '</TextOptions>';
    case 'uint':
      return '<UIntOptions>' +
               '<DefaultValue/>' +
             '</UIntOptions>';
    case 'literal':
      return '<LiteralOptions>' +
               '<DefaultValue/>' +
               '<FacetEnabled>' + (options.facetEnabled || false) +
                 '</FacetEnabled>' +
               '<ResultEnabled>' + (options.resultEnabled || false) +
                 '</ResultEnabled>' +
               '<SearchEnabled>' + (options.searchEnabled || false) +
                 '</SearchEnabled>' +
             '</LiteralOptions>';
  }
}

function createIndexFieldStatus(options) {
  return '<IndexField>' +
           '<Options>' +
             '<IndexFieldName>' + options.fieldName + '</IndexFieldName>' +
             '<IndexFieldType>' + options.fieldType + '</IndexFieldType>' +
             createIndexFieldOptionStatus(options) +
           '</Options>' +
           '<Status>' +
             '<CreationDate>' + dateFormat(options.createdAt,
                                           'isoUtcDateTime') +
               '</CreationDate>' +
             '<State>' + options.state + '</State>' +
             '<UpdateDate>' + dateFormat(options.updatedAt, 'isoUtcDateTime') +
               '</UpdateDate>' +
             '<UpdateVersion>' + (options.updateVersion || 0) +
               '</UpdateVersion>' +
           '</Status>' +
         '</IndexField>';
}

function createDefineIndexFieldResponse(options) {
  return '<?xml version="1.0"?>\n' +
         '<DefineIndexFieldResponse xmlns="' + XMLNS + '">' +
           '<DefineIndexFieldResult>' +
             createIndexFieldStatus(options) +
           '</DefineIndexFieldResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</DefineIndexFieldResponse>';
}

handlers.DefineIndexField = function(context, request, response) {
  var domain = new Domain(request, context);

  var fieldName = request.query['IndexField.IndexFieldName'] || '';
  var fieldType = request.query['IndexField.IndexFieldType'] || 'text';
  var field = domain.getIndexField(fieldName);

  var createdAt = new Date();
  var resultEnabled = fieldType != 'literal';
  try {
    field.type = fieldType;
    field.createSync();
    response.contentType('application/xml');
    response.send(createDefineIndexFieldResponse({
      fieldName: fieldName,
      fieldType: fieldType,
      facetEnabled: false,
      resultEnabled: resultEnabled,
      state: 'RequiresIndexDocuments',
      createdAt: createdAt,
      updatedAt: createdAt
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function createDeleteIndexFieldResponse(options) {
  var doc = xmlbuilder.create();

  // This is a workaround for the problem that text() does not work when empty string is given.
  // https://github.com/oozcitak/xmlbuilder-js/pull/19
  var requestId = doc.begin('DeleteIndexFieldResponse', {version: '1.0'}).attribute('xmlns', XMLNS)
    .element('DeleteIndexFieldResult')
    .up()
    .element('ResponseMetadata')
      .element('RequestId');

  if (options.requestId && options.requestId !== '') {
    requestId.text(options.requestId);
  }

  return doc.toString();
}

handlers.DeleteIndexField = function(context, request, response) {
  var domain = new Domain(request, context);

  var fieldName = request.query['IndexFieldName'] || '';
  var field = domain.getIndexField(fieldName);
  try {
    field.deleteSync();
    response.contentType('application/xml');
    response.send(createDeleteIndexFieldResponse({}));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function createIndexDocumentsResponse(options) {
  return '<?xml version="1.0"?>\n' +
         '<IndexDocumentsResponse xmlns="' + XMLNS + '">' +
           '<IndexDocumentsResult>' +
             '<FieldNames>' +
               options.fieldNames.map(function(fieldName) {
                 return '<member>' + fieldName + '</member>';
               }).join('') +
             '</FieldNames>' +
           '</IndexDocumentsResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</IndexDocumentsResponse>';
}

handlers.IndexDocuments = function(context, request, response) {
  var domain = new Domain(request, context);
  var indexFieldNames = domain.indexFields
                          .map(function(field) {
                            return field.name;
                          })
                          .sort();
  try {
    domain.reindexSync();
    response.contentType('application/xml');
    response.send(createIndexDocumentsResponse({
      fieldNames: indexFieldNames
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function escapeHTML(string) {
  return string.replace(/&/g, '&')
               .replace(/"/g, '"')
               .replace(/>/g, '>')
               .replace(/</g, '<');
}

function createUpdateSynonymOptionsResponse(options) {
  return '<UpdateSynonymOptionsResponse xmlns="' + XMLNS + '">' +
           '<UpdateSynonymOptionsResult>' +
             '<Synonyms>' +
               '<Status>' +
                 '<CreationDate>' + dateFormat(options.createdAt, 'isoUtcDateTime') + '</CreationDate>' +
                 '<UpdateVersion>' + (options.updateVersion || 0) + '</UpdateVersion>' +
                 '<State>' + (options.state || 'RequiresIndexDocuments') + '</State>' +
                 '<UpdateDate>' + dateFormat(options.updatedAt, 'isoUtcDateTime') + '</UpdateDate>' +
               '</Status>' +
               '<Options>' + escapeHTML(options.synonymOptions || '{}') + '</Options>' +
             '</Synonyms>' +
           '</UpdateSynonymOptionsResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</UpdateSynonymOptionsResponse>';
}
handlers.UpdateSynonymOptions = function(context, request, response) {
  var domain = new Domain(request, context);
  try {
    var synonymOptionsJson = request.query.Synonyms;
    var synonymOptions = JSON.parse(synonymOptionsJson);

    var updatedAt = new Date();

    domain.updateSynonymsSync(synonymOptions);

    response.send(createUpdateSynonymOptionsResponse({
      synonymOptions: JSON.stringify(synonymOptions),
      updatedAt: updatedAt,
      createdAt: updatedAt
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

exports.createHandler = function(context) {
  return function(request, response, next) {
    var message, body;

    // GCS specific behaviour: fallback to other handlers for the endpoint
    // if no action is given.
    var action = request.query.Action || '';
    if (!action)
      return next();

    var version = request.query.Version;
    if (!version) {
      message = 'An input parameter "Version" that is mandatory for ' +
                'processing the request is not supplied.';
      body = createCommonErrorResponse('MissingParameter', message);
      response.contentType('application/xml');
      return response.send(body, 400);
    }

    if (version != exports.version) {
      message = 'A bad or out-of-range value "' + version + '" was supplied ' +
                'for the "Version" input parameter.';
      body = createCommonErrorResponse('InvalidParameterValue', message);
      response.contentType('application/xml');
      return response.send(body, 400);
    }

    if (!(action in handlers)) {
      message = 'The action ' + action + ' is not valid for this web service.';
      body = createCommonErrorResponse('InvalidAction', message);
      response.contentType('application/xml');
      return response.send(body, 400);
    }

    var handler = handlers[action];
    handler(context, request, response);
  };
};
<commit_msg>Generate error response by xmlbuilder<commit_after>var path = require('path');
var nroonga = require('../../wrapped-nroonga');
var Domain = require('../../database').Domain;
var Translator = require('../../batch/translator').Translator;
var dateFormat = require('dateformat');
var xmlbuilder = require('../../xmlbuilder');

exports.version = path.basename(__dirname);

var XMLNS = 'http://cloudsearch.amazonaws.com/doc/2011-02-01';

function createCommonErrorResponse(errorCode, message) {
  var doc = xmlbuilder.create();

  doc.begin('Response', {version: '1.0'})
    .element('Errors')
      .element('Error')
        .element('Code').text(errorCode).up()
        .element('Message').text(message).up()
    .up()
    .element('RequestId').up();

  return doc.toString();
}

var handlers = Object.create(null);

function getBaseDomain(domain) {
  return domain.replace(/^cloudsearch\./, '');
}

function createDomainStatus(options) {
  return '<DomainStatus>' +
           '<Created>' + (options.created || 'false') + '</Created>' +
           '<Deleted>' + (options.deleted || 'false') + '</Deleted>' +
           '<DocService>' +
             '<Endpoint>' + (options.documentsEndpoint || '') + '</Endpoint>' +
           '</DocService>' +
           '<DomainId>' + options.domainId + '/' + options.domainName +
             '</DomainId>' +
           '<DomainName>' + options.domainName + '</DomainName>' +
           '<NumSearchableDocs>' + (options.searchableDocumentsCount || 0) +
             '</NumSearchableDocs>' +
           '<RequiresIndexDocuments>' +
             (options.requiresIndexDocuments || 'false') +
             '</RequiresIndexDocuments>' +
           '<SearchInstanceCount>' + (options.searchInstanceCount || 0) +
             '</SearchInstanceCount>' +
           '<SearchPartitionCount>' + (options.searchPartitionCount || 0) +
             '</SearchPartitionCount>' +
           '<SearchService>' +
             '<Endpoint>' + (options.searchEndpoint || '') + '</Endpoint>' +
           '</SearchService>' +
         '</DomainStatus>';
}

function createCreateDomainResponse(options) {
  return '<?xml version="1.0"?>\n' +
         '<CreateDomainResponse xmlns="' + XMLNS + '">' +
           '<CreateDomainResult>' +
             createDomainStatus(options) +
           '</CreateDomainResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</CreateDomainResponse>';
}

handlers.CreateDomain = function(context, request, response) {
  var domain = new Domain(request, context);
  try {
    domain.createSync();
    var host = getBaseDomain(request.headers.host);
    host = host ? '.' + host : '';
    response.contentType('application/xml');
    response.send(createCreateDomainResponse({
      domainName: domain.name,
      domainId: domain.id,
      searchEndpoint: 'search-' + domain.name + '-' + domain.id + host,
      documentsEndpoint: 'doc-' + domain.name + '-' + domain.id + host,
      created: true,
      searchableDocumentsCount: 0,
      searchInstanceCount: 0,
      searchPartitionCount: 0,
      requiresIndexDocuments: false
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function createDeleteDomainResponse(options) {
  return '<?xml version="1.0"?>\n' +
         '<DeleteDomainResponse xmlns="' + XMLNS + '">' +
           '<DeleteDomainResult>' +
             createDomainStatus(options) +
           '</DeleteDomainResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</DeleteDomainResponse>';
}

handlers.DeleteDomain = function(context, request, response) {
  var domain = new Domain(request, context);
  try {
    domain.deleteSync();
    var host = getBaseDomain(request.headers.host);
    host = host ? '.' + host : '';
    response.contentType('application/xml');
    response.send(createDeleteDomainResponse({
      domainName: domain.name,
      domainId: domain.id,
      searchEndpoint: 'search-' + domain.name + '-' + domain.id + host,
      documentsEndpoint: 'doc-' + domain.name + '-' + domain.id + host,
      deleted: true,
      searchableDocumentsCount: 0,
      searchInstanceCount: 0,
      searchPartitionCount: 0,
      requiresIndexDocuments: false
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function createIndexFieldOptionStatus(options) {
  switch (options.fieldType) {
    case 'text':
      return '<TextOptions>' +
               '<DefaultValue/>' +
               '<FacetEnabled>' + (options.facetEnabled || false) +
                 '</FacetEnabled>' +
               '<ResultEnabled>' + (options.resultEnabled || false) +
                 '</ResultEnabled>' +
             '</TextOptions>';
    case 'uint':
      return '<UIntOptions>' +
               '<DefaultValue/>' +
             '</UIntOptions>';
    case 'literal':
      return '<LiteralOptions>' +
               '<DefaultValue/>' +
               '<FacetEnabled>' + (options.facetEnabled || false) +
                 '</FacetEnabled>' +
               '<ResultEnabled>' + (options.resultEnabled || false) +
                 '</ResultEnabled>' +
               '<SearchEnabled>' + (options.searchEnabled || false) +
                 '</SearchEnabled>' +
             '</LiteralOptions>';
  }
}

function createIndexFieldStatus(options) {
  return '<IndexField>' +
           '<Options>' +
             '<IndexFieldName>' + options.fieldName + '</IndexFieldName>' +
             '<IndexFieldType>' + options.fieldType + '</IndexFieldType>' +
             createIndexFieldOptionStatus(options) +
           '</Options>' +
           '<Status>' +
             '<CreationDate>' + dateFormat(options.createdAt,
                                           'isoUtcDateTime') +
               '</CreationDate>' +
             '<State>' + options.state + '</State>' +
             '<UpdateDate>' + dateFormat(options.updatedAt, 'isoUtcDateTime') +
               '</UpdateDate>' +
             '<UpdateVersion>' + (options.updateVersion || 0) +
               '</UpdateVersion>' +
           '</Status>' +
         '</IndexField>';
}

function createDefineIndexFieldResponse(options) {
  return '<?xml version="1.0"?>\n' +
         '<DefineIndexFieldResponse xmlns="' + XMLNS + '">' +
           '<DefineIndexFieldResult>' +
             createIndexFieldStatus(options) +
           '</DefineIndexFieldResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</DefineIndexFieldResponse>';
}

handlers.DefineIndexField = function(context, request, response) {
  var domain = new Domain(request, context);

  var fieldName = request.query['IndexField.IndexFieldName'] || '';
  var fieldType = request.query['IndexField.IndexFieldType'] || 'text';
  var field = domain.getIndexField(fieldName);

  var createdAt = new Date();
  var resultEnabled = fieldType != 'literal';
  try {
    field.type = fieldType;
    field.createSync();
    response.contentType('application/xml');
    response.send(createDefineIndexFieldResponse({
      fieldName: fieldName,
      fieldType: fieldType,
      facetEnabled: false,
      resultEnabled: resultEnabled,
      state: 'RequiresIndexDocuments',
      createdAt: createdAt,
      updatedAt: createdAt
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function createDeleteIndexFieldResponse(options) {
  var doc = xmlbuilder.create();

  // This is a workaround for the problem that text() does not work when empty string is given.
  // https://github.com/oozcitak/xmlbuilder-js/pull/19
  var requestId = doc.begin('DeleteIndexFieldResponse', {version: '1.0'}).attribute('xmlns', XMLNS)
    .element('DeleteIndexFieldResult')
    .up()
    .element('ResponseMetadata')
      .element('RequestId');

  if (options.requestId && options.requestId !== '') {
    requestId.text(options.requestId);
  }

  return doc.toString();
}

handlers.DeleteIndexField = function(context, request, response) {
  var domain = new Domain(request, context);

  var fieldName = request.query['IndexFieldName'] || '';
  var field = domain.getIndexField(fieldName);
  try {
    field.deleteSync();
    response.contentType('application/xml');
    response.send(createDeleteIndexFieldResponse({}));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function createIndexDocumentsResponse(options) {
  return '<?xml version="1.0"?>\n' +
         '<IndexDocumentsResponse xmlns="' + XMLNS + '">' +
           '<IndexDocumentsResult>' +
             '<FieldNames>' +
               options.fieldNames.map(function(fieldName) {
                 return '<member>' + fieldName + '</member>';
               }).join('') +
             '</FieldNames>' +
           '</IndexDocumentsResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</IndexDocumentsResponse>';
}

handlers.IndexDocuments = function(context, request, response) {
  var domain = new Domain(request, context);
  var indexFieldNames = domain.indexFields
                          .map(function(field) {
                            return field.name;
                          })
                          .sort();
  try {
    domain.reindexSync();
    response.contentType('application/xml');
    response.send(createIndexDocumentsResponse({
      fieldNames: indexFieldNames
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

function escapeHTML(string) {
  return string.replace(/&/g, '&')
               .replace(/"/g, '"')
               .replace(/>/g, '>')
               .replace(/</g, '<');
}

function createUpdateSynonymOptionsResponse(options) {
  return '<UpdateSynonymOptionsResponse xmlns="' + XMLNS + '">' +
           '<UpdateSynonymOptionsResult>' +
             '<Synonyms>' +
               '<Status>' +
                 '<CreationDate>' + dateFormat(options.createdAt, 'isoUtcDateTime') + '</CreationDate>' +
                 '<UpdateVersion>' + (options.updateVersion || 0) + '</UpdateVersion>' +
                 '<State>' + (options.state || 'RequiresIndexDocuments') + '</State>' +
                 '<UpdateDate>' + dateFormat(options.updatedAt, 'isoUtcDateTime') + '</UpdateDate>' +
               '</Status>' +
               '<Options>' + escapeHTML(options.synonymOptions || '{}') + '</Options>' +
             '</Synonyms>' +
           '</UpdateSynonymOptionsResult>' +
           '<ResponseMetadata>' +
             '<RequestId>' + (options.requestId || '') + '</RequestId>' +
           '</ResponseMetadata>' +
         '</UpdateSynonymOptionsResponse>';
}
handlers.UpdateSynonymOptions = function(context, request, response) {
  var domain = new Domain(request, context);
  try {
    var synonymOptionsJson = request.query.Synonyms;
    var synonymOptions = JSON.parse(synonymOptionsJson);

    var updatedAt = new Date();

    domain.updateSynonymsSync(synonymOptions);

    response.send(createUpdateSynonymOptionsResponse({
      synonymOptions: JSON.stringify(synonymOptions),
      updatedAt: updatedAt,
      createdAt: updatedAt
    }));
  } catch(error) {
    var body = createCommonErrorResponse('InternalFailure', error.message);
    response.contentType('application/xml');
    response.send(body, 400);
  }
};

exports.createHandler = function(context) {
  return function(request, response, next) {
    var message, body;

    // GCS specific behaviour: fallback to other handlers for the endpoint
    // if no action is given.
    var action = request.query.Action || '';
    if (!action)
      return next();

    var version = request.query.Version;
    if (!version) {
      message = 'An input parameter "Version" that is mandatory for ' +
                'processing the request is not supplied.';
      body = createCommonErrorResponse('MissingParameter', message);
      response.contentType('application/xml');
      return response.send(body, 400);
    }

    if (version != exports.version) {
      message = 'A bad or out-of-range value "' + version + '" was supplied ' +
                'for the "Version" input parameter.';
      body = createCommonErrorResponse('InvalidParameterValue', message);
      response.contentType('application/xml');
      return response.send(body, 400);
    }

    if (!(action in handlers)) {
      message = 'The action ' + action + ' is not valid for this web service.';
      body = createCommonErrorResponse('InvalidAction', message);
      response.contentType('application/xml');
      return response.send(body, 400);
    }

    var handler = handlers[action];
    handler(context, request, response);
  };
};
<|endoftext|>