sketch out some service code
Matt S Trout [Sat, 8 Jan 2011 03:40:50 +0000 (03:40 +0000)]
lib/App/Clifton/Component.pm [new file with mode: 0644]
lib/App/Clifton/ConsoleService.pm [new file with mode: 0644]
lib/App/Clifton/IRCService.pm [new file with mode: 0644]
lib/App/Clifton/JabberService.pm [new file with mode: 0644]
lib/App/Clifton/Launcher.pm [new file with mode: 0644]
lib/App/Clifton/Server.pm [new file with mode: 0644]
lib/App/Clifton/Service.pm [new file with mode: 0644]
lib/App/Clifton/ServiceContainer.pm [new file with mode: 0644]
lib/App/Clifton/Task.pm [new file with mode: 0644]

diff --git a/lib/App/Clifton/Component.pm b/lib/App/Clifton/Component.pm
new file mode 100644 (file)
index 0000000..2a51217
--- /dev/null
@@ -0,0 +1,45 @@
+package App::Clifton::Component;
+
+# define this up here to avoid uninitialised warnings
+sub _debug_self {
+  my ($self, $args) = @_;
+  "${self}: ".join ', ', map "$_ => ".$args->{$_}, keys %$args;
+}
+
+use Log::Contextual qw(:log);
+use Moo;
+
+extends 'IO::Async::Notifier';
+
+sub BUILD {
+  my ($self, $args) = @_;
+  log_debug {
+    "Constructing "._debug_self($self, $args);
+  };
+  if (my $parent = $args->{parent_component}) {
+    $parent->add_child($self);
+  }
+}
+
+sub configure {
+  # If we called the superclass method, any ->new params that were populated
+  # into attributes would cause a croak. While the loss of error checking is
+  # annoying I've got other things to fix right now.
+}
+
+sub _new_child {
+  my ($self, $class, $args) = @_;
+  $class->new(%{$args||{}}, parent_component => $self);
+}
+
+sub _schedule {
+  my ($self, $code) = @_;
+  $self->get_loop->later($code);
+}
+
+sub DESTROY {
+  my ($self) = @_;
+  log_debug { "Destroying "._debug_self($self, $self) };
+}
+
+1;
diff --git a/lib/App/Clifton/ConsoleService.pm b/lib/App/Clifton/ConsoleService.pm
new file mode 100644 (file)
index 0000000..0bb27a2
--- /dev/null
@@ -0,0 +1,7 @@
+package App::Clifton::ConsoleService;
+
+use Moo;
+
+extends 'App::Clifton::Service';
+
+1;
diff --git a/lib/App/Clifton/IRCService.pm b/lib/App/Clifton/IRCService.pm
new file mode 100644 (file)
index 0000000..eeb7a70
--- /dev/null
@@ -0,0 +1,7 @@
+package App::Clifton::IRCService;
+
+use Moo;
+
+extends 'App::Clifton::Service';
+
+1;
diff --git a/lib/App/Clifton/JabberService.pm b/lib/App/Clifton/JabberService.pm
new file mode 100644 (file)
index 0000000..73002ba
--- /dev/null
@@ -0,0 +1,7 @@
+package App::Clifton::JabberService;
+
+use Moo;
+
+extends 'App::Clifton::Service';
+
+1;
diff --git a/lib/App/Clifton/Launcher.pm b/lib/App/Clifton/Launcher.pm
new file mode 100644 (file)
index 0000000..ba8356f
--- /dev/null
@@ -0,0 +1,29 @@
+package App::Clifton::Launcher;
+
+use IO::Async::Loop;
+use Log::Contextual qw(set_logger :log);
+use Log::Contextual::SimpleLogger;
+use aliased 'App::Clifton::Server';
+use Moo;
+
+has config_file => (is => 'ro', required => 1);
+
+sub run {
+  my ($self) = @_;
+  set_logger(Log::Contextual::SimpleLogger->new({ levels => [ qw(
+    info warn debug
+  ) ] }));
+  my $loop = IO::Async::Loop->new;
+  my $server = Server->new(
+    config_file => $self->config_file,
+    loop => $loop,
+  );
+  $loop->later(sub {
+    $server->reload_config->on_finished(sub {
+      log_info { "Server startup complete" };
+    });
+  });
+  $loop->loop_forever;
+}
+
+1;
diff --git a/lib/App/Clifton/Server.pm b/lib/App/Clifton/Server.pm
new file mode 100644 (file)
index 0000000..400b929
--- /dev/null
@@ -0,0 +1,35 @@
+package App::Clifton::Server;
+
+use aliased 'App::Clifton::ServiceContainer';
+use aliased 'App::Clifton::ConfigLoader';
+use Moo;
+
+extends 'App::Clifton::Service';
+
+sub BUILD {
+  my ($self, $args) = @_;
+  $args->{loop}->add($self);
+  $self->$_ for qw(services);
+}
+
+has config_file => (is => 'ro', required => 1);
+
+has config_loader => (is => 'lazy');
+
+sub _build_config_loader { ConfigLoader->new }
+
+has services => (is => 'lazy');
+
+sub _build_services {
+  shift->_new_child(ServiceContainer, {});
+}
+
+sub reload_config { shift->_do(reload_config => @_) }
+sub shutdown { shift->_do(shutdown => @_) }
+
+sub _body_for_reload_config {
+  my ($self, $args) = @_;
+  $args->{on_finished}->();
+}
+
+1;
diff --git a/lib/App/Clifton/Service.pm b/lib/App/Clifton/Service.pm
new file mode 100644 (file)
index 0000000..043e877
--- /dev/null
@@ -0,0 +1,23 @@
+package App::Clifton::Service;
+
+use aliased 'App::Clifton::Task';
+use Moo;
+
+extends 'App::Clifton::Component';
+
+sub _do {
+  my ($self, $do, @args) = @_;
+  my $body = do {
+    my $body_call = $self->_capture_weakself("_body_for_${do}");
+    sub { $body_call->(@args, @_) }
+  };
+  my $deps = {};
+  if (my $dep_call = $self->can("_dependencies_for_${do}")) {
+    $deps = $self->$dep_call(@args);
+  }
+  $self->_new_child(Task, {
+    name => $do, body => $body, dependencies => $deps
+  });
+}
+
+1;
diff --git a/lib/App/Clifton/ServiceContainer.pm b/lib/App/Clifton/ServiceContainer.pm
new file mode 100644 (file)
index 0000000..a426a01
--- /dev/null
@@ -0,0 +1,38 @@
+package App::Clifton::ServiceContainer;
+
+use aliased 'App::Clifton::JabberService';
+use aliased 'App::Clifton::IRCService';
+use aliased 'App::Clifton::ConsoleService';
+use Log::Contextual qw(:log);
+use Moo;
+
+extends 'App::Clifton::Component';
+
+has jabber => (is => 'lazy');
+has irc => (is => 'lazy');
+has console => (is => 'lazy');
+
+sub BUILD {
+  my ($self) = @_;
+  $self->$_ for qw(console irc jabber);
+}
+
+sub _build_jabber {
+  my ($self) = @_;
+  log_debug { "Spawning jabber service" };
+  $self->_new_child(JabberService, { irc_service => $self->irc });
+}
+
+sub _build_irc {
+  my ($self) = @_;
+  log_debug { "Spawning IRC service" };
+  $self->_new_child(IRCService, {});
+}
+
+sub _build_console {
+  my ($self) = @_;
+  log_debug { "Spawning console service" };
+  $self->_new_child(ConsoleService, {});
+}
+
+1;
diff --git a/lib/App/Clifton/Task.pm b/lib/App/Clifton/Task.pm
new file mode 100644 (file)
index 0000000..216e3c3
--- /dev/null
@@ -0,0 +1,63 @@
+package App::Clifton::Task;
+
+use Log::Contextual qw(:log);
+use Async::MergePoint;
+use Moo;
+
+extends 'App::Clifton::Component';
+
+has on_finished => (is => 'rw');
+
+has name => (is => 'ro', required => 1);
+
+has body => (is => 'ro', required => 1);
+
+has dependencies => (is => 'ro', default => sub { {} });
+
+has merge_point => (is => 'lazy');
+
+sub _build_merge_point { Async::MergePoint->new }
+
+sub BUILD {
+  my ($self) = @_;
+  my $deps = $self->dependencies;
+  if (my @needs = keys %$deps) {
+    my $mp = $self->merge_point;
+    $mp->needs(@needs);
+    foreach my $key (@needs) {
+      $deps->{$key}->on_finished(sub { $mp->done($key => $_[0]) });
+    }
+    $mp->close(on_finished => $self->_capture_weakself('_schedule_body'));
+  } else {
+    $self->_schedule_body;
+  }
+}
+
+sub _schedule_body {
+  my ($self, %args) = @_;
+  my $fire_body = $self->_capture_weakself('_fire_body');
+  $args{on_finished} = $self->_finished_callback;
+  $self->_schedule(sub { $fire_body->(\%args); });
+}
+
+sub _fire_body {
+  my $self = shift;
+  $self->body->(@_);
+}
+
+sub _finished_callback {
+  my ($self) = @_;
+  $self->_capture_weakself('_schedule_finished');
+}
+
+sub _schedule_finished {
+  my ($self, @args) = @_;
+  if (my $on_finished = $self->on_finished) {
+    $self->_schedule(sub { $on_finished->(@args) });
+  }
+  $self->parent->remove_child($self);
+#$self->$::Dwarn;
+#warn Devel::FindRef::track $self;
+}
+
+1;