173 lines
4.9 KiB
Perl
173 lines
4.9 KiB
Perl
|
|
# This code was forked from the LiveJournal project owned and operated
|
||
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
||
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
||
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
||
|
|
# currently be found at:
|
||
|
|
#
|
||
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
||
|
|
#
|
||
|
|
# In accordance with the original license, this code and all its
|
||
|
|
# modifications are provided under the GNU General Public License.
|
||
|
|
# A copy of that license can be found in the LICENSE file included as
|
||
|
|
# part of this distribution.
|
||
|
|
|
||
|
|
package LJ::Directory::Constraint;
|
||
|
|
use strict;
|
||
|
|
use warnings;
|
||
|
|
use Carp qw(croak);
|
||
|
|
use Digest::SHA1 qw(sha1_hex);
|
||
|
|
|
||
|
|
use LJ::Directory::SetHandle;
|
||
|
|
|
||
|
|
use LJ::Directory::Constraint::Interest;
|
||
|
|
use LJ::Directory::Constraint::UpdateTime;
|
||
|
|
use LJ::Directory::Constraint::Trusts;
|
||
|
|
use LJ::Directory::Constraint::TrustedBy;
|
||
|
|
use LJ::Directory::Constraint::Watches;
|
||
|
|
use LJ::Directory::Constraint::WatchedBy;
|
||
|
|
use LJ::Directory::Constraint::MemberOf;
|
||
|
|
use LJ::Directory::Constraint::Location;
|
||
|
|
use LJ::Directory::Constraint::JournalType;
|
||
|
|
use LJ::Directory::Constraint::Test;
|
||
|
|
|
||
|
|
use DW::BlobStore;
|
||
|
|
|
||
|
|
sub constraints_from_formargs {
|
||
|
|
my ( $pkg, $postargs ) = @_;
|
||
|
|
|
||
|
|
my @ret;
|
||
|
|
foreach my $type (
|
||
|
|
qw(Location UpdateTime Interest Trusts TrustedBy Watches WatchedBy MemberOf JournalType))
|
||
|
|
{
|
||
|
|
my $class = "LJ::Directory::Constraint::$type";
|
||
|
|
my $con = eval { $class->new_from_formargs($postargs) };
|
||
|
|
if ( ref $con eq 'ARRAY' ) {
|
||
|
|
push @ret, @$con;
|
||
|
|
}
|
||
|
|
elsif ($con) {
|
||
|
|
push @ret, $con;
|
||
|
|
}
|
||
|
|
elsif ($@) {
|
||
|
|
warn "Error loading constraint $type: $@";
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
return @ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub deserialize {
|
||
|
|
my ( $pkg, $str ) = @_;
|
||
|
|
$str =~ s/^(.+?):// or return undef;
|
||
|
|
my $type = $1;
|
||
|
|
my %args = map { LJ::durl($_) } split( /[=&]/, $str );
|
||
|
|
return bless \%args, "LJ::Directory::Constraint::$type";
|
||
|
|
}
|
||
|
|
|
||
|
|
sub serialize {
|
||
|
|
my $self = shift;
|
||
|
|
my $type = ref $self;
|
||
|
|
$type =~ s/^LJ::Directory::Constraint:://;
|
||
|
|
return "$type:"
|
||
|
|
. join( "&",
|
||
|
|
map { LJ::eurl($_) . "=" . LJ::eurl( $self->{$_} ) }
|
||
|
|
grep { /^[a-z]/ && $self->{$_} }
|
||
|
|
sort
|
||
|
|
keys %$self );
|
||
|
|
}
|
||
|
|
|
||
|
|
# default is one minute, should override in subclasses
|
||
|
|
sub cache_for {
|
||
|
|
my $self = shift;
|
||
|
|
return 60;
|
||
|
|
}
|
||
|
|
|
||
|
|
# digest of canonicalized $self
|
||
|
|
sub cache_key {
|
||
|
|
my $self = shift;
|
||
|
|
return $self->{cache_key} ||= sha1_hex( $self->serialize );
|
||
|
|
}
|
||
|
|
|
||
|
|
# returns memcache key to find this's constraint's sethandle, if cached
|
||
|
|
sub memkey {
|
||
|
|
my $self = shift;
|
||
|
|
return "dsh:" . $self->cache_key;
|
||
|
|
}
|
||
|
|
|
||
|
|
# returns cached sethandle if it exists, otherwise undef
|
||
|
|
sub cached_sethandle {
|
||
|
|
my $self = shift;
|
||
|
|
|
||
|
|
# check memcache to see if there is a sethandle
|
||
|
|
my $seth_serialized = LJ::MemCache::get( $self->memkey );
|
||
|
|
if ($seth_serialized) {
|
||
|
|
my $seth = LJ::Directory::SetHandle->new_from_string($seth_serialized);
|
||
|
|
return $seth;
|
||
|
|
}
|
||
|
|
|
||
|
|
# no handle in memcache, check dirmogsethandles table to see if there
|
||
|
|
# is a mogile handle for us
|
||
|
|
my $dbr = LJ::get_db_reader() or die "Could not get DB reader";
|
||
|
|
my ($exptime) = $dbr->selectrow_array( "SELECT exptime FROM dirmogsethandles WHERE conskey=?",
|
||
|
|
undef, $self->cache_key );
|
||
|
|
die "For $self: " . $dbr->errstr if $dbr->err;
|
||
|
|
|
||
|
|
if ($exptime) {
|
||
|
|
|
||
|
|
# there is an entry for this, make sure it isn't expired
|
||
|
|
if ( $exptime > time() ) {
|
||
|
|
|
||
|
|
# not expired, return mogile sethandle, should be valid
|
||
|
|
return LJ::Directory::SetHandle::Mogile->new( $self->cache_key );
|
||
|
|
} # otherwise it's expired, ignore it
|
||
|
|
}
|
||
|
|
|
||
|
|
return undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
# test cache first, return sethandle, or generate set, and return sethandle.
|
||
|
|
sub sethandle {
|
||
|
|
my $self = shift;
|
||
|
|
|
||
|
|
my $cached = $self->cached_sethandle;
|
||
|
|
return $cached if $cached;
|
||
|
|
|
||
|
|
my $cachekey = $self->cache_key;
|
||
|
|
|
||
|
|
my @uids = $self->matching_uids;
|
||
|
|
my $seth;
|
||
|
|
|
||
|
|
if ( @uids <= 4000 ) {
|
||
|
|
$seth = LJ::Directory::SetHandle::Inline->new(@uids);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
my $dbh = LJ::get_db_writer()
|
||
|
|
or die "Could not get db writer";
|
||
|
|
|
||
|
|
# register this mogile key
|
||
|
|
$dbh->do( "REPLACE INTO dirmogsethandles (conskey, exptime) VALUES (?, UNIX_TIMESTAMP()+?)",
|
||
|
|
undef, $cachekey, ( $self->cache_for || 0 ) );
|
||
|
|
die $dbh->errstr if $dbh->err;
|
||
|
|
|
||
|
|
my $content = join( '', map { pack( "N*", $_ ) } @uids );
|
||
|
|
DW::BlobStore->store( directorysearch => "dsh:$cachekey", \$content );
|
||
|
|
|
||
|
|
$seth = LJ::Directory::SetHandle::Mogile->new($cachekey);
|
||
|
|
}
|
||
|
|
|
||
|
|
# put in memcache:
|
||
|
|
LJ::MemCache::set( $self->memkey, $seth->as_string, $self->cache_for );
|
||
|
|
|
||
|
|
return $seth;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub matching_uids {
|
||
|
|
my $self = shift;
|
||
|
|
die "matching_uids not implemented on $self";
|
||
|
|
}
|
||
|
|
|
||
|
|
# override in subclasses
|
||
|
|
# return expected cardinality of this constraint, between 0 and 1
|
||
|
|
sub cardinality { 0 }
|
||
|
|
|
||
|
|
1;
|