Apache-Session-Browseable

 view release on metacpan or  search on metacpan

lib/Apache/Session/Browseable/Redis.pm  view on Meta::CPAN


sub searchOn {
    my ( $class, $args, $selectField, $value, @fields ) = @_;

    my %res = ();
    if ( $class->isIndexed( $args, $selectField ) ) {

        my $redisObj  = $class->_getRedis($args);
        my $index_key = "${selectField}_$value";
        my @keys      = $redisObj->smembers($index_key);
        foreach my $k (@keys) {
            next unless ($k);
            my $tmp = $redisObj->get($k);
            unless ($tmp) {
                # Lazy cleanup: remove orphan from index
                eval { $redisObj->srem( $index_key, $k ) };
                next;
            }
            eval {
                $tmp = unserialize($tmp);
                if (@fields) {
                    $res{$k}->{$_} = $tmp->{$_} foreach (@fields);
                }
                else {
                    $res{$k} = $tmp;
                }
            };
            if ($@) {
                print STDERR "Error in session $k: $@\n";
                delete $res{$k};
            }
        }
    }
    else {
        $class->get_key_from_all_sessions(
            $args,
            sub {
                my $entry = shift;
                my $id    = shift;
                return undef
                  unless ( defined $entry->{$selectField}
                    and $entry->{$selectField} eq $value );
                if (@fields) {
                    $res{$id}->{$_} = $entry->{$_} foreach (@fields);
                }
                else {
                    $res{$id} = $entry;
                }
                undef;
            }
        );
    }
    return \%res;
}

sub searchOnExpr {
    my ( $class, $args, $selectField, $value, @fields ) = @_;
    my %res;
    if ( $class->isIndexed( $args, $selectField ) ) {
        my $redisObj = $class->_getRedis($args);
        my $cursor   = 0;
        do {
            my ( $new_cursor, $sets ) =
              $redisObj->scan( $cursor, MATCH => "${selectField}_$value" );
            foreach my $set (@$sets) {
                next unless $redisObj->type($set) eq 'set';
                my @keys = $redisObj->smembers($set);
                foreach my $k (@keys) {
                    my $v = $redisObj->get($k);
                    unless ($v) {
                        # Lazy cleanup: remove orphan from index
                        eval { $redisObj->srem( $set, $k ) };
                        next;
                    }
                    my $tmp = unserialize($v);
                    if ($tmp) {
                        $res{$k} = $class->extractFields( $tmp, @fields );
                    }
                }
            }
            $cursor = $new_cursor;
        } while ( $cursor != 0 );
    }
    else {
        $value = quotemeta($value);
        $value =~ s/\\\*/\.\*/g;
        $value = qr/^$value$/;
        $class->get_key_from_all_sessions(
            $args,
            sub {
                my ( $entry, $id ) = @_;
                return undef unless ( $entry->{$selectField} =~ $value );
                $res{$id} = $class->extractFields( $entry, @fields );
                undef;
            }
        );
    }
    return \%res;
}

sub deleteIfLowerThan {
    my ( $class, $args, $rule ) = @_;
    my $deleted  = 0;
    my $redisObj = $class->_getRedis($args);
    my $index =
      ref( $args->{Index} )
      ? $args->{Index}
      : [ split /\s+/, $args->{Index} ];

    $class->get_key_from_all_sessions(
        $args,
        sub {
            my ( $v, $k ) = @_;
            if ( $rule->{not} ) {
                foreach ( keys %{ $rule->{not} } ) {
                    if (defined( $v->{$_} ) and $v->{$_} eq $rule->{not}->{$_}) {
                        return ();
                    }
                }
            }
            # Empty or data-less sessions should be purged
            my $dominated = 0;
            if ( !$v || !%$v || !exists $v->{_session_id} ) {
                $dominated = 1;
            }
            elsif ( $rule->{or} ) {
                foreach ( keys %{ $rule->{or} } ) {
                    if ( !defined( $v->{$_} ) ) {
                        # Session missing a required field: treat as expired
                        $dominated = 1;
                        last;
                    }
                    if ( $v->{$_} < $rule->{or}->{$_} ) {
                        $dominated = 1;
                        last;
                    }
                }
            }
            elsif ( $rule->{and} ) {
                my $res = 1;
                foreach ( keys %{ $rule->{and} } ) {
                    $res = 0

lib/Apache/Session/Browseable/Redis.pm  view on Meta::CPAN

                      or $v->{$_} < $rule->{and}->{$_};
                }
                $dominated = $res;
            }
            if ($dominated) {
                # Clean up index entries before deleting the session
                my $index_ok = 1;
                foreach my $i (@$index) {
                    my $t = $v->{$i};
                    next unless ( defined($t) and length($t) > 0 );
                    eval { $redisObj->srem( "${i}_$t", $k ) };
                    if ($@) {
                        warn "Failed to remove '$k' from index '${i}_$t': $@";
                        $index_ok = 0;
                    }
                }
                if ($index_ok) {
                    $redisObj->del($k);
                    $deleted++;
                }
                else {
                    warn "Skipping deletion of session '$k' due to index cleanup failure";
                }
            }
            return ();
        },
    );
    return ( 1, $deleted );
}

sub extractFields {
    my ( $class, $entry, @fields ) = @_;
    my $res;
    if (@fields) {
        $res->{$_} = $entry->{$_} foreach (@fields);
    }
    else {
        $res = $entry;
    }
    return $res;
}

sub isIndexed {
    my ( $class, $args, $field ) = @_;
    my $indexes =
      ref( $args->{Index} ) ? $args->{Index} : [ split /\s+/, $args->{Index} ];
    return grep { $_ eq $field } @$indexes;
}

sub isLlngKey {
    my ( $class, $args, $name ) = @_;
    my $expr = $args->{keysRe} || '^[0-9a-f]{32,}$';
    return ( $name =~ /$expr/o );
}

sub get_key_from_all_sessions {
    my ( $class, $args, $data ) = @_;
    my %res;

    my $redisObj = $class->_getRedis($args);
    my $cursor   = 0;
    do {
        my ( $new_cursor, $keys ) = $redisObj->scan($cursor);
        foreach my $k (@$keys) {

            # Keep only our keys
            next unless $class->isLlngKey( $args, $k );

            # Don't scan sets,...
            next unless $redisObj->type($k) eq 'string';
            eval {
                my $v = $redisObj->get($k);
                next unless $v;
                my $tmp = unserialize($v);
                if ( ref($data) eq 'CODE' ) {
                    $tmp = &$data( $tmp, $k );
                    $res{$k} = $tmp if ( defined($tmp) );
                }
                elsif ($data) {
                    $data = [$data] unless ( ref($data) );
                    $res{$k}->{$_} = $tmp->{$_} foreach (@$data);
                }
                else {
                    $res{$k} = $tmp;
                }
            };
            if ($@) {
                print STDERR "Error in session $k: $@\n";

                # Don't delete, it may own to another app
                #delete $res{$k};
            }
        }
        $cursor = $new_cursor;
    } while ( $cursor != 0 );
    return \%res;
}

sub _getRedis {
    my ( $class, $args ) = @_;
    return Apache::Session::Browseable::Store::Redis->_getRedis($args);
}

1;
__END__

=head1 NAME

Apache::Session::Browseable::Redis - Add index and search methods to
Apache::Session::Redis

=head1 SYNOPSIS

  use Apache::Session::Browseable::Redis;

  my $args = {
       server => '127.0.0.1:6379',

       # Select database (optional)
       #database => 0,

       # Use a persistent connection to the Redis server
       # (value is the connection cache key)
       # You'll probably also want to set
       # read_timeout, write_timeout, reconnect and every
       reuse => "myserver",

       # Choose your browseable fields
       Index          => 'uid mail',

       # Optional: set a Redis TTL on session keys (in seconds)
       # TTL => 86400,
  };
  
  # Use it like Apache::Session
  my %session;
  tie %session, 'Apache::Session::Browseable::Redis', $id, $args;
  $session{uid} = 'me';
  $session{mail} = 'me@me.com';
  $session{unindexedField} = 'zz';
  untie %session;
  
  # Apache::Session::Browseable add some global class methods
  #
  # 1) search on a field (indexed or not)
  my $hash = Apache::Session::Browseable::Redis->searchOn( $args, 'uid', 'me' );
  foreach my $id (keys %$hash) {
    print $id . ":" . $hash->{$id}->{mail} . "\n";
  }

  # 2) Parse all sessions
  # a. get all sessions
  my $hash = Apache::Session::Browseable::Redis->get_key_from_all_sessions($args);

  # b. get some fields from all sessions



( run in 0.637 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )