File Coverage

blib/lib/NetServer/Portal.pm
Criterion Covered Total %
statement 46 163 28.2
branch 1 68 1.4
condition 0 17 0.0
subroutine 15 30 50.0
pod 0 7 0.0
total 62 285 21.7


line stmt bran cond sub pod time code
1 1     1   964 use strict;
  1         2  
  1         57  
2             package NetServer::Portal;
3 1     1   602 use Event 0.70 qw(time);
  1         12129  
  1         8  
4 1     1   3326 use Carp;
  1         3  
  1         67  
5 1     1   754 use Symbol;
  1         844  
  1         58  
6 1     1   873 use Socket;
  1         10011  
  1         581  
7 1     1   967 use Storable 0.6 qw(store retrieve);
  1         3740  
  1         79  
8 1     1   22133 use Sys::Hostname;
  1         2107  
  1         82  
9 1     1   8 use constant NICE => -1;
  1         2  
  1         74  
10 1     1   6 use base 'Exporter';
  1         2  
  1         128  
11 1         1291 use vars qw($VERSION @EXPORT_OK $BasePort $Host $Port %PortInfo
12 1     1   46 $StoreFile $StoreTop $Storer);
  1         3  
13             $VERSION = '1.08';
14             @EXPORT_OK = qw($Host $Port %PortInfo term);
15              
16             $BasePort = 7000;
17             $Host = eval { hostname } || 'somewhere';
18              
19             $StoreFile = $0;
20             $StoreFile =~ s,^.*/,,;
21             $StoreFile =~ s/[-\._]//g;
22             $StoreFile = "/var/tmp/$StoreFile" . '.npc';
23              
24             my $terminal;
25             sub term {
26 0 0   0 0 0 return $terminal
27             if $terminal;
28 0         0 require Term::Cap;
29 0         0 $terminal = Term::Cap->Tgetent({ TERM => 'xterm', OSPEED => 9600 });
30             }
31              
32             sub register {
33 2     2 0 5 shift;
34 2         8 my %attr = @_;
35 2 50       8 confess "no package" if !exists $attr{package};
36 2         8 $PortInfo{ $attr{package} } = \%attr;
37             }
38              
39             sub set_storefile {
40 0     0 0   my ($class, $path) = @_;
41 0           $StoreFile = $path;
42             }
43              
44             sub default_start {
45 0     0 0   require NetServer::Portal::Top;
46 0           require NetServer::Portal::Pi;
47 0           eval {
48 0           my $sock = NetServer::Portal->new_socket();
49 0           NetServer::Portal->start($sock);
50             # warn "Listening on ".(7000+($$%1000))."\n";
51             };
52 0 0         if ($@) { warn; return }
  0            
  0            
53             }
54              
55             sub new_socket {
56 0     0 0   my ($class, $port) = @_;
57 0   0       $Port = $port || $BasePort + $$ % 1000;
58            
59             # Mostly snarfed from perlipc example; thanks!
60 0           my $proto = getprotobyname('tcp');
61 0           my $sock = gensym;
62 0 0         socket($sock, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
63 0 0         setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, pack('l', 1))
64             or die "setsockopt: $!";
65 0 0         bind($sock, sockaddr_in($Port, INADDR_ANY)) or die "bind: $!";
66 0           listen($sock, SOMAXCONN);
67 0           $sock;
68             }
69              
70             sub start {
71 0     0 0   my ($class, $sock) = @_;
72            
73 0           eval { $StoreTop = retrieve($StoreFile) };
  0            
74 0 0         if ($@) {
75 0 0         if ($@ =~ /No such file/) {
76             # ok
77             } else {
78 0           warn $@;
79             }
80 0           $StoreTop = {};
81             };
82             $Storer =
83             Event->idle(desc => "NetServer::Portal $StoreFile", parked=>1,
84             min => 15, max => 300, nice => 1, cb => sub {
85 0     0     store $StoreTop, $StoreFile;
86 0           });
87              
88 0           Event->io(fd => $sock, nice => NICE, cb => \&service_client,
89             desc => "NetServer::Portal");
90             }
91              
92             sub service_client {
93 0     0 0   my ($e) = @_;
94 0           my $sock = gensym;
95 0 0         my $paddr = accept $sock, $e->w->fd or die "accept: $!";
96 0           my ($port,$iaddr) = sockaddr_in($paddr);
97 0   0       (bless {
98             from => gethostbyaddr($iaddr, AF_INET) || inet_ntoa($iaddr),
99             }, 'NetServer::Portal::Client')->init($sock);
100             }
101              
102             package NetServer::Portal::Client;
103 1     1   7 use Carp;
  1         3  
  1         91  
104 1     1   7 use constant NICE => -1;
  1         3  
  1         232  
105              
106 1     1   8 use vars qw($Clients);
  1         2  
  1         67  
107             $Clients = 0;
108              
109             require NetServer::Portal::Login;
110 1     1   13 use constant LOGIN => 'NetServer::Portal::Login';
  1         1  
  1         1535  
111              
112             sub init {
113 0     0     my ($o, $sock) = @_;
114 0 0         if (!$Clients) {
115 0           $NetServer::Portal::Storer->repeat(1);
116 0           $NetServer::Portal::Storer->start;
117             }
118 0           ++$Clients;
119 0           $o->{io} = Event->io(fd => $sock, nice => NICE,
120             cb => [$o, 'cmd'],
121             desc => ref($o)." $o->{from}");
122 0           $o->set_screen(LOGIN);
123 0           $o->refresh;
124             }
125              
126             sub set_screen {
127 0     0     my ($o, $to) = @_;
128              
129 0 0 0       $to = $o->{prev_screen} if
130             $to && $to eq 'back';
131 0 0         if ($o->{screen}) {
132 0 0 0       $to = LOGIN
133             if $to && $to eq ref $o->{screen};
134 0           $o->{prev_screen} = ref $o->{screen};
135 0 0         $o->{screen}->leave
136             if $o->{screen}->can('leave');
137 0           $o->{io}->timeout(undef);
138             }
139              
140 0           my $login = $o->{screens}{ &LOGIN };
141 0 0         my $user = $login->{user} if
142             $login;
143 0 0 0       $o->{screens}{$to} = $to->new($o, $user) if
144             $to && !exists $o->{screens}{$to};
145 0 0         if ($to) {
146 0 0         if ($user) {
147 0           my $c = $o->conf;
148 0           $c->{screen} = $to;
149             }
150 0           $o->{screen} = $o->{screens}{$to};
151 0 0         die "$to->new failed"
152             if !ref $o->{screen};
153 0           $o->{screen}{error} = '';
154 0 0         $o->{screen}->enter($o)
155             if $o->{screen}->can('enter');
156             } else {
157             # logging out
158             }
159 0           $o->{needs_clear}=1;
160 0           $o->{screen}
161             }
162              
163             sub conf {
164 0     0     my ($o, $pkg) = @_;
165 0           my $login = $o->{screens}{ &LOGIN };
166 0 0         confess "eh?" if !$login;
167 0           my $user = $login->{user};
168 0 0         if (!$pkg) {
169 0           $NetServer::Portal::StoreTop->{$user}
170             } else {
171 0   0       $NetServer::Portal::StoreTop->{$user}{$pkg} ||= bless {}, $pkg;
172             }
173             }
174              
175             sub format_line {
176 0     0     my ($o) = @_;
177 0           my $col = $o->conf->{cols} - 1;
178             sub {
179 0     0     my $l;
180 0 0         if (@_ == 0) {
    0          
181 0           $l = '';
182             } elsif (@_ == 1) {
183 0           $l = $_[0]
184             } else {
185 0           my $fmt = shift @_;
186 0           $l = sprintf $fmt, @_;
187             }
188 0 0         if (length $l < $col) { $l .= ' 'x($col - length $l); }
  0 0          
189 0           elsif (length $l > $col) { $l = substr($l,0,$col) }
190 0           $l .= "\n";
191 0           $l;
192             }
193 0           }
194              
195             sub refresh {
196 0     0     my ($o) = @_;
197              
198 0           my $buf;
199 0 0         if ($o->{needs_clear}) {
200 0           $o->{needs_clear} = 0;
201 0           $buf .= NetServer::Portal::term->Tputs('cl',1,$o->{io}->fd);
202             }
203 0           $buf .= $o->{screen}->update($o);
204              
205             # Deliberately ignore partial writes. We do *not* want to block
206             # here! It is better to send half a screen and let the user
207             # request an explicit update.
208             #
209 0 0         return $o->cancel if !defined syswrite $o->{io}->fd, $buf, length $buf;
210             }
211              
212             sub cmd {
213 0     0     my ($o, $e) = @_;
214 0 0         if ($e->got eq 't') {
215 0           $o->refresh;
216 0           return;
217             }
218 0           my $in;
219 0 0         return $o->cancel if !sysread $e->w->fd, $in, 200;
220              
221 0 0         if ($in =~ s/\s*\n$//) {
222             #ok
223             } else {
224 0           $o->refresh; # ^C pressed
225 0           return;
226             }
227 0           $in =~ s/^\s+//;
228 0           $o->{screen}{error} = '';
229              
230 0 0         if ($in =~ m/^\!/) {
231 0           $o->{screens}{&LOGIN}->cmd($o, $in);
232             } else {
233 0           $o->{screen}->cmd($o, $in);
234             }
235 0 0         $o->refresh
236             if $o->{io};
237             }
238              
239             sub cancel {
240 0     0     my ($o) = @_;
241 0           --$Clients;
242 0 0         if (!$Clients) {
243 0           $NetServer::Portal::Storer->repeat(0);
244 0           $NetServer::Portal::Storer->now;
245             }
246             # warn "$o->cancel\n";
247 0           $o->set_screen(); # leave
248 0           close $o->{io}->fd;
249 0           $o->{io}->cancel;
250 0           $o->{io} = undef;
251             }
252              
253             1;
254              
255             __END__