+std::wstring channel_grid_command(command_context& ctx)
+{
+ int index = 1;
+ auto self = ctx.channels.back();
+
+ core::diagnostics::scoped_call_context save;
+ core::diagnostics::call_context::for_thread().video_channel = ctx.channels.size();
+
+ std::vector<std::wstring> params;
+ params.push_back(L"SCREEN");
+ params.push_back(L"0");
+ params.push_back(L"NAME");
+ params.push_back(L"Channel Grid Window");
+ auto screen = ctx.consumer_registry->create_consumer(params, &self.channel->stage(), get_channels(ctx));
+
+ self.channel->output().add(screen);
+
+ for (auto& channel : ctx.channels)
+ {
+ if (channel.channel != self.channel)
+ {
+ core::diagnostics::call_context::for_thread().layer = index;
+ auto producer = ctx.producer_registry->create_producer(get_producer_dependencies(self.channel, ctx), L"route://" + boost::lexical_cast<std::wstring>(channel.channel->index()) + L" NO_AUTO_DEINTERLACE");
+ self.channel->stage().load(index, producer, false);
+ self.channel->stage().play(index);
+ index++;
+ }
+ }
+
+ auto num_channels = ctx.channels.size() - 1;
+ int square_side_length = std::ceil(std::sqrt(num_channels));
+
+ ctx.channel_index = self.channel->index();
+ ctx.channel = self;
+ ctx.parameters.clear();
+ ctx.parameters.push_back(boost::lexical_cast<std::wstring>(square_side_length));
+ mixer_grid_command(ctx);
+
+ return L"202 CHANNEL_GRID OK\r\n";
+}
+
+// Thumbnail Commands
+
+void thumbnail_list_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"List thumbnails.");
+ sink.syntax(L"THUMBNAIL LIST {[sub_directory:string]}");
+ sink.para()->text(L"Lists thumbnails.");
+ sink.para()
+ ->text(L"if the optional ")->code(L"sub_directory")
+ ->text(L" is specified only the thumbnails in that sub directory will be returned.");
+ sink.para()->text(L"Examples:");
+ sink.example(
+ L">> THUMBNAIL LIST\n"
+ L"<< 200 THUMBNAIL LIST OK\n"
+ L"<< \"AMB\" 20130301T124409 1149\n"
+ L"<< \"foo/bar\" 20130523T234001 244");
+}
+
+std::wstring thumbnail_list_command(command_context& ctx)
+{
+ std::wstring sub_directory;
+
+ if (!ctx.parameters.empty())
+ sub_directory = ctx.parameters.at(0);
+
+ std::wstringstream replyString;
+ replyString << L"200 THUMBNAIL LIST OK\r\n";
+
+ for (boost::filesystem::recursive_directory_iterator itr(get_sub_directory(env::thumbnail_folder(), sub_directory)), end; itr != end; ++itr)
+ {
+ if (boost::filesystem::is_regular_file(itr->path()))
+ {
+ if (!boost::iequals(itr->path().extension().wstring(), L".png"))
+ continue;
+
+ auto relativePath = get_relative_without_extension(itr->path(), env::thumbnail_folder());
+ auto str = relativePath.generic_wstring();
+
+ if (str[0] == '\\' || str[0] == '/')
+ str = std::wstring(str.begin() + 1, str.end());
+
+ auto mtime = boost::filesystem::last_write_time(itr->path());
+ auto mtime_readable = boost::posix_time::to_iso_wstring(boost::posix_time::from_time_t(mtime));
+ auto file_size = boost::filesystem::file_size(itr->path());
+
+ replyString << L"\"" << str << L"\" " << mtime_readable << L" " << file_size << L"\r\n";
+ }
+ }
+
+ replyString << L"\r\n";
+
+ return boost::to_upper_copy(replyString.str());
+}
+
+void thumbnail_retrieve_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Retrieve a thumbnail.");
+ sink.syntax(L"THUMBNAIL RETRIEVE [filename:string]");
+ sink.para()->text(L"Retrieves a thumbnail as a base64 encoded PNG-image.");
+ sink.para()->text(L"Examples:");
+ sink.example(
+ L">> THUMBNAIL RETRIEVE foo/bar\n"
+ L"<< 201 THUMBNAIL RETRIEVE OK\n"
+ L"<< ...base64 data...");
+}
+
+std::wstring thumbnail_retrieve_command(command_context& ctx)
+{
+ std::wstring filename = env::thumbnail_folder();
+ filename.append(ctx.parameters.at(0));
+ filename.append(L".png");
+
+ std::wstring file_contents;
+
+ auto found_file = find_case_insensitive(filename);
+
+ if (found_file)
+ file_contents = read_file_base64(boost::filesystem::path(*found_file));
+
+ if (file_contents.empty())
+ CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(filename + L" not found"));
+
+ std::wstringstream reply;
+
+ reply << L"201 THUMBNAIL RETRIEVE OK\r\n";
+ reply << file_contents;
+ reply << L"\r\n";
+ return reply.str();
+}
+
+void thumbnail_generate_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Regenerate a thumbnail.");
+ sink.syntax(L"THUMBNAIL GENERATE [filename:string]");
+ sink.para()->text(L"Regenerates a thumbnail.");
+}
+
+std::wstring thumbnail_generate_command(command_context& ctx)
+{
+ if (ctx.thumb_gen)
+ {
+ ctx.thumb_gen->generate(ctx.parameters.at(0));
+ return L"202 THUMBNAIL GENERATE OK\r\n";
+ }
+ else
+ CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
+}
+
+void thumbnail_generateall_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Regenerate all thumbnails.");
+ sink.syntax(L"THUMBNAIL GENERATE_ALL");
+ sink.para()->text(L"Regenerates all thumbnails.");
+}
+
+std::wstring thumbnail_generateall_command(command_context& ctx)
+{
+ if (ctx.thumb_gen)
+ {
+ ctx.thumb_gen->generate_all();
+ return L"202 THUMBNAIL GENERATE_ALL OK\r\n";
+ }
+ else
+ CASPAR_THROW_EXCEPTION(not_supported() << msg_info(L"Thumbnail generation turned off"));
+}
+
+// Query Commands
+
+void cinf_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get information about a media file.");
+ sink.syntax(L"CINF [filename:string]");
+ sink.para()->text(L"Returns information about a media file.");
+ sink.para()->text(L"If a file with the same name exist in multiple directories, all of them are returned.");
+}
+
+std::wstring cinf_command(command_context& ctx)
+{
+ std::wstring info;
+ for (boost::filesystem::recursive_directory_iterator itr(env::media_folder()), end; itr != end; ++itr)
+ {
+ auto path = itr->path();
+ auto file = path.stem().wstring();
+ if (boost::iequals(file, ctx.parameters.at(0)))
+ info += MediaInfo(itr->path(), ctx.media_info_repo);
+ }
+
+ if (info.empty())
+ CASPAR_THROW_EXCEPTION(file_not_found());
+
+ std::wstringstream replyString;
+ replyString << L"200 CINF OK\r\n";
+ replyString << info << "\r\n";
+
+ return replyString.str();
+}
+
+void cls_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"List media files.");
+ sink.syntax(L"CLS {[sub_directory:string]}");
+ sink.para()
+ ->text(L"Lists media files in the ")->code(L"media")->text(L" folder. Use the command ")
+ ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"media")->text(L" folder.");
+ sink.para()
+ ->text(L"if the optional ")->code(L"sub_directory")
+ ->text(L" is specified only the media files in that sub directory will be returned.");
+}
+
+std::wstring cls_command(command_context& ctx)
+{
+ std::wstring sub_directory;
+
+ if (!ctx.parameters.empty())
+ sub_directory = ctx.parameters.at(0);
+
+ std::wstringstream replyString;
+ replyString << L"200 CLS OK\r\n";
+ replyString << ListMedia(ctx.media_info_repo, sub_directory);
+ replyString << L"\r\n";
+ return boost::to_upper_copy(replyString.str());
+}
+
+void fls_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"List all fonts.");
+ sink.syntax(L"FLS");
+ sink.para()
+ ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ")
+ ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder.");
+ sink.para()->text(L"Columns in order from left to right are: Font name and font path.");
+}
+
+std::wstring fls_command(command_context& ctx)
+{
+ std::wstringstream replyString;
+ replyString << L"200 FLS OK\r\n";
+
+ for (auto& font : core::text::list_fonts())
+ replyString << L"\"" << font.first << L"\" \"" << get_relative(font.second, env::font_folder()).wstring() << L"\"\r\n";
+
+ replyString << L"\r\n";
+
+ return replyString.str();
+}
+
+void tls_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"List templates.");
+ sink.syntax(L"TLS {[sub_directory:string]}");
+ sink.para()
+ ->text(L"Lists template files in the ")->code(L"templates")->text(L" folder. Use the command ")
+ ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"templates")->text(L" folder.");
+ sink.para()
+ ->text(L"if the optional ")->code(L"sub_directory")
+ ->text(L" is specified only the template files in that sub directory will be returned.");
+}
+
+std::wstring tls_command(command_context& ctx)
+{
+ std::wstring sub_directory;
+
+ if (!ctx.parameters.empty())
+ sub_directory = ctx.parameters.at(0);
+
+ std::wstringstream replyString;
+ replyString << L"200 TLS OK\r\n";
+
+ replyString << ListTemplates(ctx.cg_registry, sub_directory);
+ replyString << L"\r\n";
+
+ return replyString.str();
+}
+
+void version_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get version information.");
+ sink.syntax(L"VERSION {[component:string]}");
+ sink.para()->text(L"Returns the version of specified component.");
+ sink.para()->text(L"Examples:");
+ sink.example(
+ L">> VERSION\n"
+ L"<< 201 VERSION OK\n"
+ L"<< 2.1.0.f207a33 STABLE");
+ sink.example(
+ L">> VERSION SERVER\n"
+ L"<< 201 VERSION OK\n"
+ L"<< 2.1.0.f207a33 STABLE");
+ sink.example(
+ L">> VERSION FLASH\n"
+ L"<< 201 VERSION OK\n"
+ L"<< 11.8.800.94");
+ sink.example(
+ L">> VERSION TEMPLATEHOST\n"
+ L"<< 201 VERSION OK\n"
+ L"<< unknown");
+ sink.example(
+ L">> VERSION CEF\n"
+ L"<< 201 VERSION OK\n"
+ L"<< 3.1750.1805");
+}
+
+std::wstring version_command(command_context& ctx)
+{
+ if (!ctx.parameters.empty() && !boost::iequals(ctx.parameters.at(0), L"SERVER"))
+ {
+ auto version = ctx.system_info_repo->get_version(ctx.parameters.at(0));
+
+ return L"201 VERSION OK\r\n" + version + L"\r\n";
+ }
+
+ return L"201 VERSION OK\r\n" + env::version() + L"\r\n";
+}
+
+void info_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get a list of the available channels.");
+ sink.syntax(L"INFO");
+ sink.para()->text(L"Retrieves a list of the available channels.");
+ sink.example(
+ L">> INFO\n"
+ L"<< 200 INFO OK\n"
+ L"<< 1 720p5000 PLAYING\n"
+ L"<< 2 PAL PLAYING");
+}
+
+std::wstring info_command(command_context& ctx)
+{
+ std::wstringstream replyString;
+ // This is needed for backwards compatibility with old clients
+ replyString << L"200 INFO OK\r\n";
+ for (size_t n = 0; n < ctx.channels.size(); ++n)
+ replyString << n + 1 << L" " << ctx.channels.at(n).channel->video_format_desc().name << L" PLAYING\r\n";
+ replyString << L"\r\n";
+ return replyString.str();
+}
+
+std::wstring create_info_xml_reply(const boost::property_tree::wptree& info, std::wstring command = L"")
+{
+ std::wstringstream replyString;
+
+ if (command.empty())
+ replyString << L"201 INFO OK\r\n";
+ else
+ replyString << L"201 INFO " << std::move(command) << L" OK\r\n";
+
+ boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
+ boost::property_tree::xml_parser::write_xml(replyString, info, w);
+ replyString << L"\r\n";
+ return replyString.str();
+}
+
+void info_channel_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get information about a channel or a layer.");
+ sink.syntax(L"INFO [video_channel:int]{-[layer:int]}");
+ sink.para()->text(L"Get information about a channel or a specific layer on a channel.");
+ sink.para()->text(L"If ")->code(L"layer")->text(L" is ommitted information about the whole channel is returned.");
+}
+
+std::wstring info_channel_command(command_context& ctx)
+{
+ boost::property_tree::wptree info;
+ int layer = ctx.layer_index(std::numeric_limits<int>::min());
+
+ if (layer == std::numeric_limits<int>::min())
+ {
+ info.add_child(L"channel", ctx.channel.channel->info())
+ .add(L"index", ctx.channel_index);
+ }
+ else
+ {
+ if (ctx.parameters.size() >= 1)
+ {
+ if (boost::iequals(ctx.parameters.at(0), L"B"))
+ info.add_child(L"producer", ctx.channel.channel->stage().background(layer).get()->info());
+ else
+ info.add_child(L"producer", ctx.channel.channel->stage().foreground(layer).get()->info());
+ }
+ else
+ {
+ info.add_child(L"layer", ctx.channel.channel->stage().info(layer).get()).add(L"index", layer);
+ }
+ }
+
+ return create_info_xml_reply(info);
+}
+
+void info_template_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get information about a template.");
+ sink.syntax(L"INFO TEMPLATE [template:string]");
+ sink.para()->text(L"Gets information about the specified template.");
+}
+
+std::wstring info_template_command(command_context& ctx)
+{
+ auto filename = ctx.parameters.at(0);
+
+ std::wstringstream str;
+ str << u16(ctx.cg_registry->read_meta_info(filename));
+ boost::property_tree::wptree info;
+ boost::property_tree::xml_parser::read_xml(str, info, boost::property_tree::xml_parser::trim_whitespace | boost::property_tree::xml_parser::no_comments);
+
+ return create_info_xml_reply(info, L"TEMPLATE");
+}
+
+void info_config_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get the contents of the configuration used.");
+ sink.syntax(L"INFO CONFIG");
+ sink.para()->text(L"Gets the contents of the configuration used.");
+}
+
+std::wstring info_config_command(command_context& ctx)
+{
+ return create_info_xml_reply(caspar::env::properties(), L"CONFIG");
+}
+
+void info_paths_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get information about the paths used.");
+ sink.syntax(L"INFO PATHS");
+ sink.para()->text(L"Gets information about the paths used.");
+}
+
+std::wstring info_paths_command(command_context& ctx)
+{
+ boost::property_tree::wptree info;
+
+ info.add(L"paths.media-path", caspar::env::media_folder());
+ info.add(L"paths.log-path", caspar::env::log_folder());
+ info.add(L"paths.data-path", caspar::env::data_folder());
+ info.add(L"paths.template-path", caspar::env::template_folder());
+ info.add(L"paths.thumbnail-path", caspar::env::thumbnail_folder());
+ info.add(L"paths.font-path", caspar::env::font_folder());
+ info.add(L"paths.initial-path", caspar::env::initial_folder() + L"/");
+
+ return create_info_xml_reply(info, L"PATHS");
+}
+
+void info_system_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get system information.");
+ sink.syntax(L"INFO SYSTEM");
+ sink.para()->text(L"Gets system information like OS, CPU and library version numbers.");
+}
+
+std::wstring info_system_command(command_context& ctx)
+{
+ boost::property_tree::wptree info;
+
+ info.add(L"system.name", caspar::system_product_name());
+ info.add(L"system.os.description", caspar::os_description());
+ info.add(L"system.cpu", caspar::cpu_info());
+
+ ctx.system_info_repo->fill_information(info);
+
+ return create_info_xml_reply(info, L"SYSTEM");
+}
+
+void info_server_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get detailed information about all channels.");
+ sink.syntax(L"INFO SERVER");
+ sink.para()->text(L"Gets detailed information about all channels.");
+}
+
+std::wstring info_server_command(command_context& ctx)
+{
+ boost::property_tree::wptree info;
+
+ int index = 0;
+ for (auto& channel : ctx.channels)
+ info.add_child(L"channels.channel", channel.channel->info())
+ .add(L"index", ++index);
+
+ return create_info_xml_reply(info, L"SERVER");
+}
+
+void info_queues_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get detailed information about all AMCP Command Queues.");
+ sink.syntax(L"INFO QUEUES");
+ sink.para()->text(L"Gets detailed information about all AMCP Command Queues.");
+}
+
+std::wstring info_queues_command(command_context& ctx)
+{
+ return create_info_xml_reply(AMCPCommandQueue::info_all_queues(), L"QUEUES");
+}
+
+void info_threads_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Lists all known threads in the server.");
+ sink.syntax(L"INFO THREADS");
+ sink.para()->text(L"Lists all known threads in the server.");
+}
+
+std::wstring info_threads_command(command_context& ctx)
+{
+ std::wstringstream replyString;
+ replyString << L"200 INFO THREADS OK\r\n";
+
+ for (auto& thread : get_thread_infos())
+ {
+ replyString << thread->native_id << L" " << u16(thread->name) << L"\r\n";
+ }
+
+ replyString << L"\r\n";
+ return replyString.str();
+}
+
+void info_delay_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get the current delay on a channel or a layer.");
+ sink.syntax(L"INFO [video_channel:int]{-[layer:int]} DELAY");
+ sink.para()->text(L"Get the current delay on the specified channel or layer.");
+}
+
+std::wstring info_delay_command(command_context& ctx)
+{
+ boost::property_tree::wptree info;
+ auto layer = ctx.layer_index(std::numeric_limits<int>::min());
+
+ if (layer == std::numeric_limits<int>::min())
+ info.add_child(L"channel-delay", ctx.channel.channel->delay_info());
+ else
+ info.add_child(L"layer-delay", ctx.channel.channel->stage().delay_info(layer).get())
+ .add(L"index", layer);
+
+ return create_info_xml_reply(info, L"DELAY");
+}
+
+void diag_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Open the diagnostics window.");
+ sink.syntax(L"DIAG");
+ sink.para()->text(L"Opens the ")->see(L"Diagnostics Window")->text(L".");
+}
+
+std::wstring diag_command(command_context& ctx)
+{
+ core::diagnostics::osd::show_graphs(true);
+
+ return L"202 DIAG OK\r\n";
+}
+
+void gl_info_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Get information about the allocated and pooled OpenGL resources.");
+ sink.syntax(L"GL INFO");
+ sink.para()->text(L"Retrieves information about the allocated and pooled OpenGL resources.");
+}
+
+std::wstring gl_info_command(command_context& ctx)
+{
+ auto device = ctx.ogl_device;
+
+ if (!device)
+ CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
+
+ std::wstringstream result;
+ result << L"201 GL INFO OK\r\n";
+
+ boost::property_tree::xml_writer_settings<std::wstring> w(' ', 3);
+ auto info = device->info();
+ boost::property_tree::write_xml(result, info, w);
+ result << L"\r\n";
+
+ return result.str();
+}
+
+void gl_gc_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Release pooled OpenGL resources.");
+ sink.syntax(L"GL GC");
+ sink.para()->text(L"Releases all the pooled OpenGL resources. ")->strong(L"May cause a pause on all video channels.");
+}
+
+std::wstring gl_gc_command(command_context& ctx)
+{
+ auto device = ctx.ogl_device;
+
+ if (!device)
+ CASPAR_THROW_EXCEPTION(not_supported() << msg_info("GL command only supported with OpenGL accelerator."));
+
+ device->gc().wait();
+
+ return L"202 GL GC OK\r\n";
+}
+
+static const int WIDTH = 80;
+
+struct max_width_sink : public core::help_sink
+{
+ std::size_t max_width = 0;
+
+ void begin_item(const std::wstring& name) override
+ {
+ max_width = std::max(name.length(), max_width);
+ };
+};
+
+struct short_description_sink : public core::help_sink
+{
+ std::size_t width;
+ std::wstringstream& out;
+
+ short_description_sink(std::size_t width, std::wstringstream& out) : width(width), out(out) { }
+
+ void begin_item(const std::wstring& name) override
+ {
+ out << std::left << std::setw(width + 1) << name;
+ };
+
+ void short_description(const std::wstring& short_description) override
+ {
+ out << short_description << L"\r\n";
+ };
+};
+
+struct simple_paragraph_builder : core::paragraph_builder
+{
+ std::wostringstream out;
+ std::wstringstream& commit_to;
+
+ simple_paragraph_builder(std::wstringstream& out) : commit_to(out) { }
+ ~simple_paragraph_builder()
+ {
+ commit_to << core::wordwrap(out.str(), WIDTH) << L"\n";
+ }
+ spl::shared_ptr<paragraph_builder> text(std::wstring text) override
+ {
+ out << std::move(text);
+ return shared_from_this();
+ }
+ spl::shared_ptr<paragraph_builder> code(std::wstring txt) override { return text(std::move(txt)); }
+ spl::shared_ptr<paragraph_builder> strong(std::wstring item) override { return text(L"*" + std::move(item) + L"*"); }
+ spl::shared_ptr<paragraph_builder> see(std::wstring item) override { return text(std::move(item)); }
+ spl::shared_ptr<paragraph_builder> url(std::wstring url, std::wstring name) override { return text(std::move(url)); }
+};
+
+struct simple_definition_list_builder : core::definition_list_builder
+{
+ std::wstringstream& out;
+
+ simple_definition_list_builder(std::wstringstream& out) : out(out) { }
+ ~simple_definition_list_builder()
+ {
+ out << L"\n";
+ }
+
+ spl::shared_ptr<definition_list_builder> item(std::wstring term, std::wstring description) override
+ {
+ out << core::indent(core::wordwrap(term, WIDTH - 2), L" ");
+ out << core::indent(core::wordwrap(description, WIDTH - 4), L" ");
+ return shared_from_this();
+ }
+};
+
+struct long_description_sink : public core::help_sink
+{
+ std::wstringstream& out;
+
+ long_description_sink(std::wstringstream& out) : out(out) { }
+
+ void syntax(const std::wstring& syntax) override
+ {
+ out << L"Syntax:\n";
+ out << core::indent(core::wordwrap(syntax, WIDTH - 2), L" ") << L"\n";
+ };
+
+ spl::shared_ptr<core::paragraph_builder> para() override
+ {
+ return spl::make_shared<simple_paragraph_builder>(out);
+ }
+
+ spl::shared_ptr<core::definition_list_builder> definitions() override
+ {
+ return spl::make_shared<simple_definition_list_builder>(out);
+ }
+
+ void example(const std::wstring& code, const std::wstring& caption) override
+ {
+ out << core::indent(core::wordwrap(code, WIDTH - 2), L" ");
+
+ if (!caption.empty())
+ out << core::indent(core::wordwrap(L"..." + caption, WIDTH - 2), L" ");
+
+ out << L"\n";
+ }
+private:
+ void begin_item(const std::wstring& name) override
+ {
+ out << name << L"\n\n";
+ };
+};
+
+std::wstring create_help_list(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
+{
+ std::wstringstream result;
+ result << L"200 " << help_command << L" OK\r\n";
+ max_width_sink width;
+ ctx.help_repo->help(tags, width);
+ short_description_sink sink(width.max_width, result);
+ sink.width = width.max_width;
+ ctx.help_repo->help(tags, sink);
+ result << L"\r\n";
+ return result.str();
+}
+
+std::wstring create_help_details(const std::wstring& help_command, const command_context& ctx, std::set<std::wstring> tags)
+{
+ std::wstringstream result;
+ result << L"201 " << help_command << L" OK\r\n";
+ auto joined = boost::join(ctx.parameters, L" ");
+ long_description_sink sink(result);
+ ctx.help_repo->help(tags, joined, sink);
+ result << L"\r\n";
+ return result.str();
+}
+
+void help_describer(core::help_sink& sink, const core::help_repository& repository)
+{
+ sink.short_description(L"Show online help for AMCP commands.");
+ sink.syntax(LR"(HELP {[command:string]})");
+ sink.para()->text(LR"(Shows online help for a specific command or a list of all commands.)");
+ sink.example(L">> HELP", L"Shows a list of commands.");
+ sink.example(L">> HELP PLAY", L"Shows a detailed description of the PLAY command.");
+}
+
+std::wstring help_command(command_context& ctx)
+{
+ if (ctx.parameters.size() == 0)
+ return create_help_list(L"HELP", ctx, { L"AMCP" });
+ else
+ return create_help_details(L"HELP", ctx, { L"AMCP" });
+}
+
+void help_producer_describer(core::help_sink& sink, const core::help_repository& repository)
+{
+ sink.short_description(L"Show online help for producers.");
+ sink.syntax(LR"(HELP PRODUCER {[producer:string]})");
+ sink.para()->text(LR"(Shows online help for a specific producer or a list of all producers.)");
+ sink.example(L">> HELP PRODUCER", L"Shows a list of producers.");
+ sink.example(L">> HELP PRODUCER FFmpeg Producer", L"Shows a detailed description of the FFmpeg Producer.");
+}
+
+std::wstring help_producer_command(command_context& ctx)
+{
+ if (ctx.parameters.size() == 0)
+ return create_help_list(L"HELP PRODUCER", ctx, { L"producer" });
+ else
+ return create_help_details(L"HELP PRODUCER", ctx, { L"producer" });
+}
+
+void help_consumer_describer(core::help_sink& sink, const core::help_repository& repository)
+{
+ sink.short_description(L"Show online help for consumers.");
+ sink.syntax(LR"(HELP CONSUMER {[consumer:string]})");
+ sink.para()->text(LR"(Shows online help for a specific consumer or a list of all consumers.)");
+ sink.example(L">> HELP CONSUMER", L"Shows a list of consumers.");
+ sink.example(L">> HELP CONSUMER Decklink Consumer", L"Shows a detailed description of the Decklink Consumer.");
+}
+
+std::wstring help_consumer_command(command_context& ctx)
+{
+ if (ctx.parameters.size() == 0)
+ return create_help_list(L"HELP CONSUMER", ctx, { L"consumer" });
+ else
+ return create_help_details(L"HELP CONSUMER", ctx, { L"consumer" });
+}
+
+void bye_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Disconnect the session.");
+ sink.syntax(L"BYE");
+ sink.para()
+ ->text(L"Disconnects from the server if connected remotely, if interacting directly with the console ")
+ ->text(L"on the machine Caspar is running on then this will achieve the same as the ")->see(L"KILL")->text(L" command.");
+}
+
+std::wstring bye_command(command_context& ctx)
+{
+ ctx.client->disconnect();
+ return L"";
+}
+
+void kill_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Shutdown the server.");
+ sink.syntax(L"KILL");
+ sink.para()->text(L"Shuts the server down.");
+}
+
+std::wstring kill_command(command_context& ctx)
+{
+ ctx.shutdown_server_now.set_value(false); //false for not attempting to restart
+ return L"202 KILL OK\r\n";
+}
+
+void restart_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Shutdown the server with restart exit code.");
+ sink.syntax(L"RESTART");
+ sink.para()
+ ->text(L"Shuts the server down, but exits with return code 5 instead of 0. ")
+ ->text(L"Intended for use in combination with ")->code(L"casparcg_auto_restart.bat");
+}
+
+std::wstring restart_command(command_context& ctx)
+{
+ ctx.shutdown_server_now.set_value(true); //true for attempting to restart
+ return L"202 RESTART OK\r\n";
+}
+
+void lock_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Lock or unlock access to a channel.");
+ sink.syntax(L"LOCK [video_channel:int] [action:ACQUIRE,RELEASE,CLEAR] {[lock-phrase:string]}");
+ sink.para()->text(L"Allows for exclusive access to a channel.");
+ sink.para()->text(L"Examples:");
+ sink.example(L">> LOCK 1 ACQUIRE secret");
+ sink.example(L">> LOCK 1 RELEASE");
+ sink.example(L">> LOCK 1 CLEAR");
+}
+
+std::wstring lock_command(command_context& ctx)
+{
+ int channel_index = boost::lexical_cast<int>(ctx.parameters.at(0)) - 1;
+ auto lock = ctx.channels.at(channel_index).lock;
+ auto command = boost::to_upper_copy(ctx.parameters.at(1));
+
+ if (command == L"ACQUIRE")
+ {
+ std::wstring lock_phrase = ctx.parameters.at(2);
+
+ //TODO: read options
+
+ //just lock one channel
+ if (!lock->try_lock(lock_phrase, ctx.client))
+ return L"503 LOCK ACQUIRE FAILED\r\n";
+
+ return L"202 LOCK ACQUIRE OK\r\n";
+ }
+ else if (command == L"RELEASE")
+ {
+ lock->release_lock(ctx.client);
+ return L"202 LOCK RELEASE OK\r\n";
+ }
+ else if (command == L"CLEAR")
+ {
+ std::wstring override_phrase = env::properties().get(L"configuration.lock-clear-phrase", L"");
+ std::wstring client_override_phrase;
+
+ if (!override_phrase.empty())
+ client_override_phrase = ctx.parameters.at(2);
+
+ //just clear one channel
+ if (client_override_phrase != override_phrase)
+ return L"503 LOCK CLEAR FAILED\r\n";
+
+ lock->clear_locks();
+
+ return L"202 LOCK CLEAR OK\r\n";
+ }
+
+ CASPAR_THROW_EXCEPTION(file_not_found() << msg_info(L"Unknown LOCK command " + command));
+}
+
+void req_describer(core::help_sink& sink, const core::help_repository& repo)
+{
+ sink.short_description(L"Perform any command with an additional request id identifying the response.");
+ sink.syntax(L"REQ [request_id:string] COMMAND...");
+ sink.para()
+ ->text(L"This special command modifies the AMCP protocol a little bit to prepend ")
+ ->code(L"RES request_id")->text(L" to the response, in order to see what asynchronous response matches what request.");
+ sink.para()->text(L"Examples:");
+ sink.example(
+ L">> REQ unique PLAY 1-0 AMB\n"
+ L"<< RES unique 202 PLAY OK");
+}
+
+
+void register_commands(amcp_command_repository& repo)