+sub wanttalk
+{
+ return _want('talk', @_);
+}
+
+sub wantgrid
+{
+ return _want('grid', @_);
+}
+
+sub wantemail
+{
+ return _want('email', @_);
+}
+
+sub wantann_talk
+{
+ return _want('ann_talk', @_);
+}
+
+sub wantpc16
+{
+ return _want('pc16', @_);
+}
+
+sub wantsendpc16
+{
+ return _want('sendpc16', @_);
+}
+
+sub wantroutepc16
+{
+ return _want('routepc16', @_);
+}
+
+sub wantusstate
+{
+ return _want('usstate', @_);
+}
+
+sub wantdxcq
+{
+ return _want('dxcq', @_);
+}
+
+sub wantdxitu
+{
+ return _want('dxitu', @_);
+}
+
+sub wantgtk
+{
+ return _want('gtk', @_);
+}
+
+sub wantpc9x
+{
+ return _want('pc9x', @_);
+}
+
+sub wantlogininfo
+{
+ my $self = shift;
+ my $val = shift;
+ $self->{wantlogininfo} = $val if defined $val;
+ return $self->{wantlogininfo};
+}
+
+sub is_node
+{
+ my $self = shift;
+ return $self->{sort} =~ /^[ACRSX]$/;
+}
+
+sub is_local_node
+{
+ my $self = shift;
+ return grep $_ eq 'local_node', @{$self->{group}};
+}
+
+sub is_user
+{
+ my $self = shift;
+ return $self->{sort} =~ /^[UW]$/;
+}
+
+sub is_web
+{
+ my $self = shift;
+ return $self->{sort} eq 'W';
+}
+
+sub is_bbs
+{
+ my $self = shift;
+ return $self->{sort} eq 'B';
+}
+
+sub is_spider
+{
+ my $self = shift;
+ return $self->{sort} eq 'S';
+}
+
+sub is_clx
+{
+ my $self = shift;
+ return $self->{sort} eq 'C';
+}
+
+sub is_dxnet
+{
+ my $self = shift;
+ return $self->{sort} eq 'X';
+}
+
+sub is_arcluster
+{
+ my $self = shift;
+ return $self->{sort} eq 'R';
+}
+
+sub is_ak1a
+{
+ my $self = shift;
+ return $self->{sort} eq 'A';
+}
+
+sub unset_passwd
+{
+ my $self = shift;
+ delete $self->{passwd};
+ $self->put;
+}
+
+sub unset_passphrase
+{
+ my $self = shift;
+ delete $self->{passphrase};
+ $self->put;
+}
+
+sub set_believe
+{
+ my $self = shift;
+ my $call = uc shift;
+ $self->{believe} ||= [];
+ unless (grep $_ eq $call, @{$self->{believe}}) {
+ push @{$self->{believe}}, $call;
+ $self->put;
+ };
+}
+
+sub unset_believe
+{
+ my $self = shift;
+ my $call = uc shift;
+ if (exists $self->{believe}) {
+ $self->{believe} = [grep {$_ ne $call} @{$self->{believe}}];
+ delete $self->{believe} unless @{$self->{believe}};
+ $self->put;
+ }
+}
+
+sub believe
+{
+ my $self = shift;
+ return exists $self->{believe} ? @{$self->{believe}} : ();
+}
+
+sub lastping
+{
+ my $self = shift;
+ my $call = shift;
+ $self->{lastping} ||= {};
+ $self->{lastping} = {} unless ref $self->{lastping};
+ my $b = $self->{lastping};
+ $b->{$call} = shift if @_;
+ return $b->{$call};
+}
+
+#
+# read in the latest version of the user file. As this file is immutable, the file one really wants is
+# a later (generated) copy. But, if the plain users.v4 file is all we have, we'll use that.
+#
+
+sub readinjson
+{
+ my $fn = $filename;
+ my $nfn = "$fn.n";
+ my $ofn = "$fn.o";
+
+ my $ta = [gettimeofday];
+ my $count = 0;
+ my $s;
+ my $err = 0;
+
+ if (-e $nfn && -e $fn && (stat($nfn))[9] > (stat($fn))[9]) {
+ # move the old file to .o
+ unlink $ofn;
+ move($fn, $ofn);
+ move($nfn, $fn);
+ };
+
+ # if we don't have a users.v4 at this point, look for a backup users.v4.json, users.v4.n then users.v4.o
+ unless (-e $fn) {
+ move($nfn, $fn) unless -e $fn; # the users.v4 isn't there (maybe convert-users-v3-to-v4.pl
+ move("$fn.json", $fn); # from a run of convert-users-v3-to-v4.pl
+ move($ofn, $fn) unless -e $fn; # desperate now...
+ }
+
+ if ($ifh) {
+ $ifh->seek(0, 0);
+ } else {
+ LogDbg("DXUser","DXUser::readinjson: opening $fn as users file");
+ $ifh = IO::File->new("+<$fn") or die "Cannot open $fn ($!)";
+ }
+ my $pos = $ifh->tell;
+ while (<$ifh>) {
+ chomp;
+ my @f = split /\t/;
+ $u{$f[0]} = [$pos];
+ $count++;
+ $pos = $ifh->tell;
+ }
+ $ifh->seek(0, 0);
+
+ # $ifh is "global" and should not be closed
+
+ dbg("DXUser::readinjson $count record headers read from $fn in ". _diffms($ta) . " mS");
+ return $totusers = $count;
+}
+
+#
+# Write a newer copy of the users.v4 file to users.v4.n, which is what will be read in.
+# This means that the existing users.v4 is not touched during a run of dxspider, or at least
+# not yet.
+
+sub writeoutjson
+{
+ my $ofn = shift || "$filename.n";
+ my $ta = [gettimeofday];
+
+ my $ofh = IO::File->new(">$ofn") or die "$ofn write error $!";
+ my $count = 0;
+ $ifh->seek(0, 0);
+ for my $k (sort keys %u) {
+ my $l = get($k, 1);
+ if ($l) {
+ chomp $l;
+ print $ofh "$k\t$l\n";
+ ++$count;
+ } else {
+ LogDbg('DXUser', "DXUser::writeoutjson callsign $k not found")
+ }
+ }
+
+ $ofh->close;
+ dbg("DXUser::writeoutjson $count records written to $ofn in ". _diffms($ta) . " mS");
+ return $count;
+}