# 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;