Skip to content

Commit

Permalink
Move the watcher cli into the util directory
Browse files Browse the repository at this point in the history
  • Loading branch information
swansontec committed Jul 16, 2015
1 parent b58f70b commit 4b7951a
Show file tree
Hide file tree
Showing 5 changed files with 603 additions and 1 deletion.
7 changes: 6 additions & 1 deletion Makefile
Expand Up @@ -25,6 +25,7 @@ abc_sources = \

cli_sources = $(wildcard cli/*.cpp cli/*/*.cpp)
test_sources = $(wildcard test/*.cpp)
watcher_sources = $(wildcard util/*.cpp)

generated_headers = \
abcd/config.h \
Expand All @@ -34,6 +35,7 @@ generated_headers = \
abc_objects = $(addprefix $(WORK_DIR)/, $(addsuffix .o, $(basename $(abc_sources))))
cli_objects = $(addprefix $(WORK_DIR)/, $(addsuffix .o, $(basename $(cli_sources))))
test_objects = $(addprefix $(WORK_DIR)/, $(addsuffix .o, $(basename $(test_sources))))
watcher_objects = $(addprefix $(WORK_DIR)/, $(addsuffix .o, $(basename $(watcher_sources))))

# Adjustable verbosity:
V ?= 0
Expand All @@ -42,7 +44,7 @@ ifeq ($V,0)
endif

# Targets:
all: $(WORK_DIR)/abc-cli check
all: $(WORK_DIR)/abc-cli check $(WORK_DIR)/abc-watcher
libabc.a: $(WORK_DIR)/libabc.a
libabc.so: $(WORK_DIR)/libabc.so

Expand All @@ -58,6 +60,9 @@ $(WORK_DIR)/abc-cli: $(cli_objects) $(WORK_DIR)/libabc.a
$(WORK_DIR)/abc-test: $(test_objects) $(WORK_DIR)/libabc.a
$(RUN) $(CXX) -o $@ $^ $(LDFLAGS) $(LIBS)

$(WORK_DIR)/abc-watcher: $(watcher_objects) $(WORK_DIR)/libabc.a
$(RUN) $(CXX) -o $@ $^ $(LDFLAGS) $(LIBS)

check: $(WORK_DIR)/abc-test
$(RUN) $<

Expand Down
3 changes: 3 additions & 0 deletions abc-watcher
@@ -0,0 +1,3 @@
#!/bin/sh
program=build/abc-watcher
make $program && ./$program "$@"
65 changes: 65 additions & 0 deletions util/ReadLine.cpp
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2014, AirBitz, Inc.
* All rights reserved.
*/

#include "ReadLine.hpp"
#include <cstring>
#include <iostream>

ReadLine::~ReadLine()
{
socket_.send("", 1);
thread_->join();
delete thread_;
}

ReadLine::ReadLine(zmq::context_t &context)
: socket_(context, ZMQ_REQ)
{
socket_.bind("inproc://terminal");
// The thread must be constructed after the socket is already bound.
// The context must be passed by pointer to avoid copying.
auto functor = std::bind(&ReadLine::run, this, &context);
thread_ = new std::thread(functor);
}

void ReadLine::show_prompt()
{
std::cout << "> " << std::flush;
socket_.send("", 0);
}

zmq_pollitem_t ReadLine::pollitem()
{
return zmq_pollitem_t
{
socket_, 0, ZMQ_POLLIN, 0
};
}

std::string ReadLine::get_line()
{
char line[1000];
size_t size = socket_.recv(line, sizeof(line), ZMQ_DONTWAIT);
return std::string(line, size);
}

void ReadLine::run(zmq::context_t *context)
{
zmq::socket_t socket(*context, ZMQ_REP);
socket.connect("inproc://terminal");

while (true)
{
// Wait for a socket request:
char request[1];
if (socket.recv(request, sizeof(request)))
return;

// Read the input:
char line[1000];
std::cin.getline(line, sizeof(line));
socket.send(line, std::strlen(line));
}
}
67 changes: 67 additions & 0 deletions util/ReadLine.hpp
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2014, AirBitz, Inc.
* All rights reserved.
*/

#ifndef UTIL_READ_LINE_HPP
#define UTIL_READ_LINE_HPP

#include <string>
#include <thread>
#include <zmq.hpp>

/*
* Reads lines from the terminal in a separate thread.
*
* A networking thread cannot use the C++ standard library to read from the
* terminal. Once the thread calls std::cin.getline or similar, it becomes
* stuck until the user types something, so the thread cannot handle network
* events at the same time. Therefore, the network stuff and the terminal
* stuff need to run in separate threads.
*
* The simplest solution is to create a thread that simply reads from the
* terminal and transmits the results over a zeromq inproc socket. The main
* thread sends an empty REQ message when it wants to read from the terminal,
* and the reader thread sends back a REP message with whatever the user
* typed. If the main thread sends a non-empty REQ message, the thread quits.
*
* To use this class, first call `show_prompt`. This call will display a
* command prompt and begin reading input in the background. Then, use
* `pollitem` with `zmq_poll` to determine when the line is available. Once
* the line is available, use `get_line` to retrieve it.
*
* If you attempt to destroy this class while reading a line, the destructor
* will block until the user finishes their entry.
*/
class ReadLine
{
public:
~ReadLine();
ReadLine(zmq::context_t &context);

/**
* Displays a command prompt and begins reading a line in the background.
*/
void show_prompt();

/**
* Creates a zeromq pollitem_t structure suitable for passing to the
* zmq_poll function. The zmq_poll function will indicate that there is
* data waiting to be read once a line is available.
*/
zmq_pollitem_t pollitem();

/**
* Retrieves the line requested by `show_prompt`. This method will
* return a blank string if no line is available yet.
*/
std::string get_line();

private:
void run(zmq::context_t *context);

zmq::socket_t socket_;
std::thread *thread_;
};

#endif

0 comments on commit 4b7951a

Please sign in to comment.