Commits

Jarrod Funnell committed 91857c4

Modules now clean up after themselves. Took a bit of a song and a dance though. Just call me Mary Poppins.

Comments (0)

Files changed (5)

 use warnings;
 use autodie;
 
-BEGIN {
-	use File::Basename;
-	use File::Spec;
-	$main::libdir = File::Spec->catdir(dirname(__FILE__), 'lib');
-	$main::confdir = File::Spec->catdir(dirname(__FILE__), 'conf');
-}
-use lib $main::libdir;
+use File::Basename;
+use File::Spec;
+
+use lib File::Spec->catdir(dirname(__FILE__), 'lib');
 
 use EV;
 use AnyEvent;
 use Coro;
 
 use Scalar::Util 'refaddr';
-
 use Try::Tiny;
+use ModHandler;
 
 my $unloop = AE::cv;
 my $outpipe = Coro::Channel->new();
 my %clients;
 
-#Later we might make a module handler/loader/etc. For now we just make them here
-use ModHandler;
-my $mods = ModHandler->new(conf_dir => $main::confdir, outpipe => $outpipe);
+my $mods = ModHandler->new(
+	conf_dir   => File::Spec->rel2abs(File::Spec->catdir(dirname(__FILE__), 'conf')),
+	device_dir => File::Spec->rel2abs(File::Spec->catdir(dirname(__FILE__), 'lib', 'Device')), 
+	outpipe    => $outpipe
+);
 
 #The tcp server merely handles incoming connections.
 sub handle_connection {
 
 #Need to store these callbacks so they don't get cleaned up. Perl trying to be too smart.
 my $traps = [
-	AE::signal("INT"  => sub { $unloop->send() }),
-	AE::signal("TERM" => sub { $unloop->send() }),
-	AE::signal("HUP"  => sub { $unloop->send() }),
-	AE::signal("PIPE" => sub { $unloop->send() }),
+	AE::signal("INT"  => sub { $mods->DESTROY; $unloop->send() }),
+	AE::signal("TERM" => sub { $mods->DESTROY; $unloop->send() }),
+	AE::signal("HUP"  => sub { $mods->DESTROY; $unloop->send() }),
+	AE::signal("PIPE" => sub { $mods->DESTROY; $unloop->send() }),
 ];
 
 $unloop->recv();
 
 package Device;
 use Mouse;
+use AnyEvent;
 
 has 'units' => (
 	is       => 'ro',
 	$self->thread;
 }
 
-sub DEMOLISH {
+sub unload {
 	my $self = shift;
 	while (my ($setting, $state) = each %{$self->exit_cmds}){
 		$self->set_mode($setting, $state);

lib/Device/ASLF250.pm

 				say "Failed to parse temperature! Value was: $line";
 			}
 		}
-		#Turns out if we don't destroy the handle, a whole lot of bad shit happens! And I spend like an hour debugging to figure it out!
-		$self->handle->destroy;
+		#Turns out if we don't destroy the handle properly, a whole lot of bad shit happens! 
+		#AND AGAIN I spend like an hour debugging to figure it out!
+		$self->unload;
+		$self->handle->push_shutdown;
 		
 		$self->outpipe->put({
 			error =>{

lib/Device/Test.pm

 	{}
 }
 
+sub connect {
+	my $fh = IO::File->new('/dev/null')
+		or die "Can't open /dev/null: $!";
+	my $ufh = AnyEvent::Handle->new(fh => $fh)
+		or die "Can't create async handle: $!";
+	return $ufh;
+}
+
 sub _build_thread {
 	my $self = shift;
 	my $quit = 0;

lib/ModHandler.pm

 
 package ModHandler;
 use Mouse;
+use AnyEvent;
 use AnyEvent::Filesys::Notify;
+use File::Spec;
 use Module::Load;
 use YAML::XS qw(LoadFile DumpFile);
 
 	required => 1,
 );
 
+has 'device_dir' => (
+	is       => 'ro',
+	required => 1,
+);
+
 has 'watcher' => (
 	is       => 'ro',
 	builder  => '_build_watcher',
 			my (@events) = @_;
 			for my $event (@events){
 				next if $event->is_dir();
-				$self->load_module($event->path) if $event->is_created || $event->is_modified;
+				$self->load_module($event->path) if $event->is_modified;
 			}
 		},
 	);
 
 sub BUILD {
 	my $self = shift;
+
+	my $mod_glob = File::Spec->catfile($self->device_dir, '*.pm');
+	say "loading $mod_glob";
+	for my $modfile (glob $mod_glob) {
+		load $modfile;
+	}
 	
-	for my $yamlfile (glob ($self->conf_dir . '/*.yml')){
+	my $yml_glob = File::Spec->catfile($self->conf_dir, '*.yml');
+	for my $yamlfile (glob $yml_glob){
 		$self->load_module($yamlfile);
 	}
 }
 sub load_module {
 	my $self = shift;
 	my $file = shift;
-	
 	my $conf = LoadFile($file)
 		or die "Cannot load $file, ", $!;
 	
-	#Unload the module so we can load it again.
-	if (my $mod = $self->modules->{$conf->{id}}){
-		say "Unloading module for '$conf->{id}'";
-		$mod->send_input(undef, undef, "Module unloaded");
-		#Force the module thread to end. Otherwise the dying object will mess everything up.
-		$mod->thread->cede_to;
-	}
-	#If we are just unloading, clean up and leave.
-	if (!$conf->{enabled}){
-		delete $self->modules->{$conf->{id}}
-			if defined $self->modules->{$conf->{id}};
-		return;
-	}
+	my $mod_id = $conf->{id};
+
+	#Unload the module (if needed)
+	$self->unload_module($mod_id) if (defined $self->modules->{$mod_id});
+
+	#Leave if nothing is to be loaded
+	return unless $conf->{enabled};
 	
-	say "Loading module for '$conf->{id}'";
+	say "Loading module for '$mod_id'";
 	
 	my $modulename = $conf->{module};
-	load $modulename;
-	
 	my $instance = $modulename->new(
 		interface => $conf->{interface},
 		outpipe   => $self->outpipe,
 		init_cmds => $conf->{init} // {},
 		exit_cmds => $conf->{exit} // {},
 	);
-	$self->modules->{$conf->{id}} = $instance;
+	$self->modules->{$mod_id} = $instance;
+
+}
+
+sub unload_module {
+	my $self   = shift;
+	my $mod_id = shift;
+
+	say "Unloading module for '$mod_id'";
+	
+	my $dead_mod = delete $self->modules->{$mod_id};
+	$dead_mod->send_input(undef, undef, "Module unloaded");
 
+	#Force the module thread to end. Otherwise the dying object will mess everything up.
+	$dead_mod->thread->cede_to;
 }
 
+#Safe cleanup, call from signal handlers
+sub DEMOLISH {
+	my $self = shift;
+	$self->unload_module($_) for keys %{$self->modules};
+}
+
+
 __PACKAGE__->meta->make_immutable();