use v6.d;
class Model {
has Lock::Async $!lock .= new;
has BagHash $!words .= new;
method add_words(@words --> Promise) {
return $!lock.lock.then: {
LEAVE $!lock.unlock;
++$!words{$_} for @words;
}
}
method get_most_common_pairs(Int() $how_many=10 --> Promise) {
return $!lock.lock.then: {
LEAVE $!lock.unlock;
reverse $!words.sort(*.value).head($how_many);
}
}
}
class View {
method words_from_input(Str() $input --> Seq) {
return $input.split(/\s+/,:skip-empty);
}
method table_from_ranked_pairs(@ranked_pairs) {
return @ranked_pairs.map({ sprintf '%-15s %3d',$^p.key,$^p.value }).join("\n");
}
}
class HTTPController {
use Cro::HTTP::Router;
has $.model is required;
has $.view is required;
sub respond($body) {
response.status = 200;
response.set-body("{$body}\n");
}
method routes() {
return route {
get -> *@, :$n! is query {
self.get_words($n);
}
get -> *@ {
self.get_words();
}
post -> *@ {
request-body-text -> $body {
self.post_words($body);
}
}
}
}
method get_words($n=10) {
my @most_common_pairs = await $!model.get_most_common_pairs($n);
my $table = $!view.table_from_ranked_pairs(@most_common_pairs);
respond($table);
}
method post_words($body) {
my @words = $!view.words_from_input($body);
await $!model.add_words(@words);
respond('ok');
}
}
class LineController {
has $.model is required;
has $.view is required;
method on_command(:$stream,:$command,:$args_string) {
if $command eq 'get' {
self.get_words($stream,$args_string);
}
elsif $command eq 'put' {
self.put_words($stream,$args_string);
}
else {
reply($stream,"bad command, only 'get' and 'put'");
}
}
sub reply($stream,$text) {
await $stream.print("$text\n");
}
method get_words($stream,$args_string) {
my $how_many = $args_string || 10;
my @most_common_pairs = await $!model.get_most_common_pairs($how_many);
my $table = $!view.table_from_ranked_pairs(@most_common_pairs);
reply($stream,$table);
}
method put_words($stream,$args_string) {
my @words = $!view.words_from_input($args_string);
await $!model.add_words(@words);
reply($stream,'ok');
}
}
class TextServer {
has $.controller is required;
has $.host is required;
has $.port is required;
has $!stop = Promise.new;
method start() {
start react {
whenever IO::Socket::Async.listen($!host,$!port) -> $conn {
whenever $conn.Supply.lines -> $line {
my ($command,$args_string) = $line.split(/\s+/,2);
$.controller.on_command(
stream => $conn,
:$command, :$args_string
);
}
}
whenever $!stop -> {
last;
}
};
}
method stop() { $!stop.keep }
}
use Cro::HTTP::Server;
my Model $model .= new;
my View $view .= new;
my HTTPController $httpcontroller .= new(:$model,:$view);
my $httpserver = Cro::HTTP::Server.new(
host => 'localhost',
port => 8080,
application => $httpcontroller.routes,
);
$httpserver.start;
my LineController $linecontroller .= new(:$model,:$view);
my TextServer $textserver .=new(
host => 'localhost',
port => 2020,
controller => $linecontroller,
);
$textserver.start;
react whenever signal(SIGINT) {
$httpserver.stop;
$textserver.stop;
exit;
}