PageRenderTime 42ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/OurNet/BBS/Base.pm

https://github.com/audreyt/ournet-bbs
Perl | 581 lines | 383 code | 135 blank | 63 comment | 53 complexity | c3224da3c56dade1c42856801bc1f0b5 MD5 | raw file
  1. # $File: //depot/libOurNet/BBS/lib/OurNet/BBS/Base.pm $ $Author: autrijus $
  2. # $Revision: #8 $ $Change: 3850 $ $DateTime: 2003/01/25 20:03:29 $
  3. package OurNet::BBS::Base;
  4. use 5.006;
  5. use strict;
  6. no warnings 'deprecated';
  7. use constant EGO => 0; use constant FLAG => 1;
  8. use constant HASH => 1; use constant ARRAY => 2;
  9. use constant CODE => 3; use constant GLOB => 4;
  10. use constant TYPES => [qw/_ego _hash _array _code _glob/];
  11. use constant SIGILS => [qw/$ % @ & */];
  12. require PerlIO if $] >= 5.008;
  13. # These magical hashes below holds all cached initvar constants:
  14. # = subrountines as $RegSub{$glob}
  15. # = module imports as $RegMod{$glob}
  16. # = variables as $RegVar{$class}{$sym}
  17. my (%RegVar, %RegSub, %RegMod);
  18. my %Packlists; # $packlist cache for contains()
  19. ## Class Methods ######################################################
  20. # These methods expects a package name as their first argument.
  21. # constructor method; turn into an pseudo hash if _phash exists
  22. use constant CONSTRUCTOR => << '.';
  23. sub __PKG__::new {
  24. my __PACKAGE__ $self = bless([\%{__PKG__::FIELDS}], '__PACKAGE__');
  25. # eval {
  26. if (ref($_[1])) {
  27. # Passed in a single hashref -- assign it!
  28. %{$self} = %{$_[1]};
  29. }
  30. else {
  31. # Automagically fill in the fields.
  32. $self->{$_} = $_[$self->[0]{$_}] foreach ((__KEYS__)[0 .. $#_-1]);
  33. }
  34. # };
  35. # require Carp and Carp::confess($@) if $@;
  36. __TIE__
  37. return $self->{_ego} = bless (\[$self, __OBJ__], '__PKG__');
  38. }
  39. 1;
  40. .
  41. # import does following things:
  42. # 1. set up @ISA.
  43. # 2. export type constants.
  44. # 3. set overload bits.
  45. # 4. install accessor methods.
  46. # 5. handle variable propagation.
  47. # 6. install the new() handler.
  48. require overload; # no import, please
  49. sub import {
  50. my $class = shift;
  51. my $pkg = caller(0);
  52. no strict 'refs';
  53. no warnings 'once';
  54. # in non-direct usage, only ournet client gets symbols and sigils.
  55. my $is_client = ($pkg eq 'OurNet::BBS::Client' or $pkg eq 'OurNet::BBS::OurNet::BBS');
  56. return unless $class eq __PACKAGE__ or $is_client;
  57. *{"$pkg\::$_"} = \&{$_} foreach qw/EGO FLAG HASH ARRAY CODE GLOB/;
  58. return *{"$pkg\::SIGILS"} = \&{SIGILS} if $is_client;
  59. *{"$pkg\::ego"} = sub { ${$_[0]}->[0] };
  60. push @{"$pkg\::ISA"}, $class;
  61. my (@overload, $tie_eval, $obj_eval);
  62. my $fields = \%{"$pkg\::FIELDS"};
  63. foreach my $type (HASH .. GLOB) {
  64. if (exists($fields->{TYPES->[$type]})) { # checks for _hash .. _glob
  65. my $sigil = SIGILS->[$type];
  66. push @overload, "$sigil\{}" => sub {
  67. # use Carp; eval { ${$_[0]}->[$type] } || Carp::confess($@)
  68. ${$_[0]}->[$type]
  69. };
  70. if ($type == HASH or $type == ARRAY) {
  71. $tie_eval = "tie my ${sigil}obj => '$pkg', ".
  72. "[\$self, $type];\n" . $tie_eval;
  73. $obj_eval .= ", \\${sigil}obj";
  74. }
  75. elsif ($type == CODE) {
  76. $tie_eval .= 'my $code = sub { $self->refresh(undef, CODE);'.
  77. '$self->{_code}(@_) };';
  78. $obj_eval .= ', $code';
  79. }
  80. elsif ($type == GLOB) {
  81. $tie_eval = 'my $glob = \$self->{_glob};' . $tie_eval;
  82. $obj_eval .= ', $glob';
  83. }
  84. }
  85. else {
  86. $obj_eval .= ', undef';
  87. }
  88. }
  89. $obj_eval =~ s/(?:, undef)+$//;
  90. my $sub_new = CONSTRUCTOR;
  91. my $keys = join(' ', sort {
  92. $fields->{$a} <=> $fields->{$b}
  93. } grep {
  94. /^[^_]/
  95. } keys(%{$fields}));
  96. $sub_new =~ s/__TIE__/$tie_eval/g;
  97. $sub_new =~ s/__OBJ__/$obj_eval/g;
  98. $sub_new =~ s/__PKG__/$pkg/g;
  99. $sub_new =~ s/__KEYS__/qw{$keys}/g;
  100. $sub_new =~ s/__PACKAGE__/OurNet::BBS::Base/g;
  101. unless (eval $sub_new) {
  102. require Carp;
  103. Carp::confess "$sub_new\n\n$@";
  104. }
  105. $pkg->overload::OVERLOAD(
  106. @overload,
  107. '""' => sub { overload::AddrRef($_[0]) },
  108. '0+' => sub { 0 },
  109. 'bool' => sub { 1 },
  110. 'cmp' => sub { "$_[0]" cmp "$_[1]" },
  111. '<=>' => sub { "$_[0]" cmp "$_[1]" }, # for completeness' sake
  112. );
  113. # install accessor methods
  114. unless (UNIVERSAL::can($pkg, '__accessor')) {
  115. foreach my $property (keys(%{"$pkg\::FIELDS"}), '__accessor') {
  116. *{"$pkg\::$property"} = sub {
  117. my $self = ${$_[0]}->[EGO];
  118. $self->refresh_meta;
  119. $self->{$property} = $_[1] if $#_;
  120. return $self->{$property};
  121. };
  122. }
  123. }
  124. # my $backend = $1 if $pkg =~ m|^OurNet::BBS::([^:]+)|;
  125. my $backend = substr($pkg, 13, index($pkg, ':', 14) - 13); # fast
  126. my @defer; # delayed aliasing until variables are processed
  127. foreach my $parent (@{"$pkg\::ISA"}) {
  128. next if $parent eq __PACKAGE__; # Base won't use mutable variables
  129. while (my ($sym, $ref) = each(%{"$parent\::"})) {
  130. push @defer, ($pkg, $sym, $ref);
  131. }
  132. unshift @_, @{$RegMod{$parent}} if ($RegMod{$parent});
  133. }
  134. while (my ($mod, $symref) = splice(@_, 0, 2)) {
  135. if ($mod =~ m/^\w/) { # getvar from other modules
  136. push @{$RegMod{$pkg}}, $mod, $symref;
  137. require "OurNet/BBS/$backend/$mod.pm";
  138. $mod = "OurNet::BBS::$backend\::$mod";
  139. foreach my $symref (@{$symref}) {
  140. my ($ch, $sym) = CORE::unpack('a1a*', $symref);
  141. die "can't import: $mod\::$sym" unless *{"$mod\::$sym"};
  142. ++$RegVar{$pkg}{$sym};
  143. *{"$pkg\::$sym"} = (
  144. $ch eq '$' ? \${"$mod\::$sym"} :
  145. $ch eq '@' ? \@{"$mod\::$sym"} :
  146. $ch eq '%' ? \%{"$mod\::$sym"} :
  147. $ch eq '*' ? \*{"$mod\::$sym"} :
  148. $ch eq '&' ? \&{"$mod\::$sym"} : undef
  149. );
  150. }
  151. }
  152. else { # this module's own setvar
  153. my ($ch, $sym) = CORE::unpack('a1a*', $mod);
  154. *{"$pkg\::$sym"} = ($ch eq '$') ? \$symref : $symref;
  155. ++$RegVar{$pkg}{$sym};
  156. }
  157. }
  158. my @defer_sub; # further deferred subroutines that needs localizing
  159. while (my ($pkg, $sym, $ref) = splice(@defer, 0, 3)) {
  160. next if exists $RegVar{$pkg}{$sym} # already imported
  161. or *{"$pkg\::$sym"}{CODE}; # defined by use subs
  162. if (defined(&{$ref})) {
  163. push @defer_sub, ($pkg, $sym, $ref);
  164. next;
  165. }
  166. next unless ($ref =~ /^\*(.+)::(.+)/)
  167. and exists $RegVar{$1}{$2};
  168. *{"$pkg\::$sym"} = $ref;
  169. ++$RegVar{$pkg}{$sym};
  170. }
  171. # install per-package wrapper handlers for mutable variables
  172. while (my ($pkg, $sym, $ref) = splice(@defer_sub, 0, 3)) {
  173. my $ref = ($RegSub{$ref} || $ref);
  174. next unless ($ref =~ /^\*(.+)::([^:]+)$/);
  175. next if defined(&{"$pkg\::$sym"});
  176. if (%{$RegVar{$pkg}}) {
  177. eval qq(
  178. sub $pkg\::$sym {
  179. ) . join('',
  180. map { qq(
  181. local *$1\::$_ = *$pkg\::$_;
  182. )} (keys(%{$RegVar{$pkg}}))
  183. ) . qq(
  184. &{$ref}(\@_);
  185. };
  186. );
  187. }
  188. else {
  189. *{"$pkg\::$sym"} = $ref;
  190. };
  191. $RegSub{"*$pkg\::$sym"} = $ref;
  192. }
  193. return unless $OurNet::BBS::Encoding;
  194. *{"$pkg\::unpack"} = \&_unpack;
  195. *{"$pkg\::pack"} = \&_pack;
  196. }
  197. sub _unpack {
  198. require Encode;
  199. return map Encode::decode($OurNet::BBS::Encoding => $_), CORE::unpack($_[0], $_[1]);
  200. }
  201. sub _pack {
  202. require Encode;
  203. return CORE::pack($_[0], map Encode::encode($OurNet::BBS::Encoding => $_), @_[1..$#_]);
  204. }
  205. ## Instance Methods ###################################################
  206. # These methods expects a tied object as their first argument.
  207. # unties through an object to get back the true $self
  208. sub ego { $_[0] }
  209. # the all-important cache refresh instance method
  210. sub refresh {
  211. my $self = shift;
  212. my $ego;
  213. ($self, $ego) = (ref($self) eq __PACKAGE__)
  214. ? ($self->{_ego}, $self)
  215. : ($self, ${$self}->[EGO]);
  216. no strict 'refs';
  217. my $prefix = ref($self)."::refresh_";
  218. my $method = $_[0] && defined(&{"$prefix$_[0]"})
  219. ? "$prefix$_[0]" : $prefix.'meta';
  220. return $method->($ego, @_);
  221. }
  222. # opens access to connections via OurNet protocol
  223. sub daemonize {
  224. require OurNet::BBS::Server;
  225. OurNet::BBS::Server->daemonize(@_);
  226. }
  227. =begin comment
  228. # The following code doesn't work, because they always override.
  229. # permission checking; fall-back for undefined packages
  230. sub writeok {
  231. my ($self, $user, $op, $argref) = @_;
  232. print "warning: permission model for ".ref($self)." unimplemented.\n".
  233. " access forbidden for user ".$user->id().".\n"
  234. if $OurNet::BBS::DEBUG;
  235. return;
  236. }
  237. # ditto
  238. sub readok {
  239. my ($self, $user, $op, $argref) = @_;
  240. print "warning: permission model for ".ref($self)." unimplemented.\n".
  241. " access forbidden for user ".$user->id().".\n"
  242. if $OurNet::BBS::DEBUG;
  243. return;
  244. }
  245. =end comment
  246. =cut
  247. # clears internal memory; uses CLEAR instead
  248. sub purge {
  249. $_[0]->ego->{_ego}->CLEAR;
  250. }
  251. # returns the BBS backend for the object
  252. sub backend {
  253. my $backend = ref($_[0]);
  254. $backend = ref($_[0]{_ego}) if $backend eq __PACKAGE__;
  255. $backend = substr($backend, 13, index($backend, ':', 14) - 13); # fast
  256. # $backend = $1 if $backend =~ m|^OurNet::BBS::(\w+)|;
  257. return $backend;
  258. }
  259. # developer-friendly way to check files' timestamp for mtime fields
  260. sub filestamp {
  261. my ($self, $file, $field, $check_only) = @_;
  262. my $time = (stat($file))[9];
  263. no warnings 'uninitialized';
  264. return 1 if $self->{$field ||= 'mtime'} == $time;
  265. $self->{$field} = $time unless $check_only;
  266. return 0; # something changed
  267. }
  268. # developer-friendly way to check timestamp for mtime fields
  269. sub timestamp {
  270. my ($self, $time, $field, $check_only) = @_;
  271. no warnings 'uninitialized';
  272. return 1 if $self->{$field ||= 'mtime'} == $time;
  273. $self->{$field} = $time unless $check_only;
  274. return 0; # something changed
  275. }
  276. # check if something's in packlist; packages don't contain undef
  277. sub contains {
  278. my ($self, $key) = @_;
  279. $self = $self->{_ego} if ref($self) eq __PACKAGE__;
  280. no strict 'refs';
  281. no warnings 'uninitialized';
  282. # print "checking $key against $self: @{ref($self).'::packlist'}\n";
  283. return (length($key) and index(
  284. $Packlists{ref($self)} ||= " @{ref($self).'::packlist'} ",
  285. " $key ",
  286. ) > -1);
  287. }
  288. # loads a module: ($self, $backend, $module).
  289. sub fillmod {
  290. my $self = $_[0];
  291. $self =~ s|::|/|g;
  292. require "$self/$_[1]/$_[2].pm";
  293. return "$_[0]::$_[1]::$_[2]";
  294. }
  295. # create a new module and fills in arguments in the expected order
  296. sub fillin {
  297. my ($self, $key, $class) = splice(@_, 0, 3);
  298. return if defined($self->{_hash}{$key});
  299. $self->{_hash}{$key} = OurNet::BBS->fillmod(
  300. $self->{backend}, $class
  301. )->new(@_);
  302. return 1;
  303. }
  304. # returns the module in the same backend, or $val's package if supplied
  305. sub module {
  306. my ($self, $mod, $val) = @_;
  307. if ($val and UNIVERSAL::isa($val, 'UNIVERSAL')) {
  308. my $pkg = ref($val);
  309. if (UNIVERSAL::isa($val, 'HASH')) {
  310. # special case: somebody blessed a hash to put into STORE.
  311. bless $val, 'main'; # you want black magic?
  312. $_[2] = \%{$val}; # curse (unbless) it!
  313. }
  314. return $pkg;
  315. }
  316. my $backend = $self->backend;
  317. require "OurNet/BBS/$backend/$mod.pm";
  318. return "OurNet::BBS::$backend\::$mod";
  319. }
  320. # object serialization for OurNet::Server calls; does nothing otherwise
  321. sub SPAWN { return $_[0] }
  322. sub REF { return ref($_[0]) }
  323. sub KEYS { return keys(%{$_[0]}) }
  324. # XXX: Object injection
  325. sub INJECT {
  326. my ($self, $code, @param) = @_;
  327. if (UNIVERSAL::isa($code, 'CODE')) {
  328. require B::Deparse;
  329. my $deparse = B::Deparse->new(qw/-p -sT/);
  330. $code = $deparse->coderef2text($code);
  331. $code =~ s/^\s+use (?:strict|warnings)[^;\n]*;\n//m;
  332. }
  333. require Safe;
  334. my $safe = Safe->new;
  335. $safe->permit_only(qw{
  336. :base_core padsv padav padhv padany rv2gv refgen srefgen ref gvsv gv gelem
  337. });
  338. my $result = $safe->reval("sub $code");
  339. warn $@ if $@;
  340. return sub { $result->($self, @_) };
  341. }
  342. ## Tiescalar Accessors ################################################
  343. # XXX: Experimental: Globs only.
  344. sub TIESCALAR {
  345. return bless(\$_[1], $_[0]);
  346. }
  347. ## Tiearray Accessors #################################################
  348. # These methods expects a raw (untied) object as their first argument.
  349. # merged hasharray!
  350. sub TIEARRAY {
  351. return bless(\$_[1], $_[0]);
  352. }
  353. sub FETCHSIZE {
  354. my ($self, $key) = @_;
  355. my ($ego, $flag) = @{${$self}};
  356. $self->refresh(undef, ARRAY);
  357. return scalar @{$ego->{_array} ||= []};
  358. }
  359. sub PUSH {
  360. my $self = shift;
  361. my $size = $self->FETCHSIZE;
  362. foreach my $item (@_) {
  363. $self->STORE($size++, $item);
  364. }
  365. }
  366. ## Tiehash Accessors ##################################################
  367. # These methods expects a raw (untied) object as their first argument.
  368. # the Tied Hash constructor method
  369. sub TIEHASH {
  370. return bless(\$_[1], $_[0]);
  371. }
  372. # fetch accessesor
  373. sub FETCH {
  374. my ($self, $key) = @_;
  375. my ($ego, $flag) = @{${$self}};
  376. $self->refresh($key, $flag);
  377. return ($flag == HASH) ? $ego->{_hash}{$key} : $ego->{_array}[$key];
  378. }
  379. # fallback implementation to STORE
  380. sub STORE {
  381. die "@_: STORE unimplemented";
  382. }
  383. # delete an element; calls its remove() subroutine to handle actual removal
  384. sub DELETE {
  385. my ($self, $key) = @_;
  386. my ($ego, $flag) = @{${$self}};
  387. $self->refresh($key, $flag);
  388. if ($flag == HASH) {
  389. return unless exists $ego->{_hash}{$key};
  390. $ego->{_hash}{$key}->ego->remove
  391. if UNIVERSAL::can($ego->{_hash}{$key}, 'ego');
  392. return delete($ego->{_hash}{$key});
  393. }
  394. else {
  395. return unless exists $ego->{_array}[$key];
  396. $ego->{_array}[$key]->ego->remove
  397. if UNIVERSAL::can($ego->{_array}[$key], 'ego');
  398. return delete($ego->{_array}[$key]);
  399. }
  400. }
  401. # check for existence of a key.
  402. sub EXISTS {
  403. my ($self, $key) = @_;
  404. my ($ego, $flag) = @{${$self}};
  405. $self->refresh($key, $flag);
  406. return ($flag == HASH) ? exists $ego->{_hash}{$key}
  407. : exists $ego->{_array}[$key];
  408. }
  409. # iterator; this one merely uses 'scalar keys()'
  410. sub FIRSTKEY {
  411. my $self = $_[0];
  412. my $ego = ${$self}->[EGO];
  413. $ego->refresh_meta(undef, HASH);
  414. scalar keys (%{$ego->{_hash}});
  415. return $self->NEXTKEY;
  416. }
  417. # ditto
  418. sub NEXTKEY {
  419. my $self = $_[0];
  420. return each %{${$self}->[EGO]->{_hash}};
  421. }
  422. # empties the cache, do not DELETE the objects themselves
  423. sub CLEAR {
  424. my $self = ${$_[0]}->[EGO];
  425. %{$self->{_hash}} = () if (exists $self->{_hash});
  426. @{$self->{_array}} = () if (exists $self->{_array});
  427. }
  428. # could care less
  429. sub DESTROY () {};
  430. sub UNTIE () {};
  431. our $AUTOLOAD;
  432. sub AUTOLOAD {
  433. my $action = substr($AUTOLOAD, (
  434. (rindex($AUTOLOAD, ':') - 1) || return
  435. ));
  436. no strict 'refs';
  437. *{$AUTOLOAD} = sub {
  438. use Carp; confess ref($_[0]->{_ego}).$action
  439. unless defined &{ref($_[0]->{_ego}).$action};
  440. goto &{ref($_[0]->{_ego}).$action}
  441. };
  442. goto &{$AUTOLOAD};
  443. }
  444. 1;