mourningdove/cgi-bin/DW/User/Edges/WatchTrust/UserHelper.pm
2026-05-24 01:03:05 +00:00

253 lines
8.6 KiB
Perl

#!/usr/bin/perl
#
# DW::User::Edges::WatchTrust::Group
#
# This module implements helper functions to referring to a group of people
# trusted or watched by a given user. Also assists with getting data about the
# reverse relationships - trusted by, watched by.
#
# Authors:
# Mark Smith <mark@dreamwidth.org>
#
# Copyright (c) 2009 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
#
package DW::User::Edges::WatchTrust::UserHelper;
use strict;
use Carp qw/ confess /;
sub new {
my ( $pkg, $u, %args ) = @_;
my $self = bless {
u => $u,
t_rev_userids => undef, # if loaded, arrayref of userids that trust this user.
t_userids => {}, # hashref of userid => 1, for users that $u trusts.
t_mut_userids => undef, # once loaded, arrayref of mutually trusted userids
w_rev_userids => undef, # if loaded, arrayref of userids that watch this user.
w_userids => {}, # hashref of userid => 1, for users that $u watches.
w_mut_userids => undef, # once loaded, arrayref of mutually watched userids
}, $pkg;
# whether or not we can be sloppy with results on things that would
# otherwise be unbounded. see also: load_cap.
$self->{sloppy} = delete $args{sloppy};
# don't load more than 5,000 LJ::User objects when
# returning sloppy lists.
$self->{load_cap} = delete $args{load_cap} || 5000;
# should we exclude mutual watches from 'w_rev_userids'?
$self->{mutualsep} = delete $args{mutuals_separate};
# FIXME: sad that we have to pass this in, but currently
# it's not cached on the $u singleton. in future, remove this.
# it's a hashref of { $userid => 1 }, for user's trusts
$self->{t_userids} = delete $args{t_userids} || {};
$self->{w_userids} = delete $args{w_userids} || {};
# let them provide a callback to remove userids from lists.
$self->{hide_watch_test} = delete $args{hide_watch_test_cb} || sub { 0 };
confess 'unknown params' if %args;
return $self;
}
# doesn't matter in trust groups!
sub reader_count {
confess 'this function has no relevance to trust groups, please fix the caller';
}
# in scalar context, number of mutually watched users.
# in list context, LJ::User objects (sorted by display name)
sub mutually_watched_users {
my $fom = $_[0];
if (wantarray) {
return @{ $fom->_mutually_watched_users };
}
return scalar @{ $fom->_mutually_watched_users };
}
# in scalar context, number of mutually trusted users.
# in list context, LJ::User objects (sorted by display name)
sub mutually_trusted_users {
my $fom = $_[0];
if (wantarray) {
return @{ $fom->_mutually_trusted_users };
}
return scalar @{ $fom->_mutually_trusted_users };
}
# returns just inbound people/identity users (removing mutuals if specified)
# in scalar context, number of friend-ofs
# in list context, LJ::User objects
sub watched_by_users {
my $fom = shift;
if (wantarray) {
return @{ $fom->_watched_by_users };
}
# scalar context
my $ct = scalar @{ $fom->_watched_by_users };
if ( $fom->{sloppy_load} ) {
# we got sloppy results, so scalar $ct above isn't good.
# skip all filtering and just set their friend-of count to
# total edges in, less their mutual friend count if necessary
# (which generally includes all communities they're a member of,
# as people watch those)
$ct = scalar @{ $fom->_watched_by_userids };
if ( $fom->{mutualsep} ) {
$ct -= scalar @{ $fom->_mutually_watched_userids };
}
}
return $ct;
}
# in scalar context, returns count of people that trust you
# in list context, LJ::User objects
sub trusted_by_users {
my $fom = shift;
if (wantarray) {
return @{ $fom->_trusted_by_users };
}
return scalar @{ $fom->_trusted_by_users };
}
# --------------------------------------------------------------------------
# Internals
# --------------------------------------------------------------------------
# return arrayref of userids with friendof edges to this user.
sub _trusted_by_userids {
my $fom = $_[0];
return $fom->{t_rev_userids} ||= [ $fom->{u}->trusted_by_userids ];
}
# return arrayref of userids with friendof edges to this user.
sub _watched_by_userids {
my $fom = $_[0];
return $fom->{w_rev_userids} ||= [ $fom->{u}->watched_by_userids ];
}
# returns arrayref of LJ::User mutually trusted, filter (visible people), and sorted by display name
sub _mutually_trusted_users {
my $fom = $_[0];
return $fom->{t_mut_users} if $fom->{t_mut_users};
# because outbound relationships are capped, so then is this load_userids call
my @ids = @{ $fom->_mutually_trusted_userids };
my $us = LJ::load_userids(@ids);
return $fom->{t_mut_users} = [
sort { $a->display_name cmp $b->display_name }
grep { $_->statusvis =~ /[VML]/ && $_->is_individual }
map { $us->{$_} ? ( $us->{$_} ) : () } @ids
];
}
# returns arrayref of LJ::User mutually watched, filter (visible people), and sorted by display name
sub _mutually_watched_users {
my $fom = $_[0];
return $fom->{w_mut_users} if $fom->{w_mut_users};
# because outbound relationships are capped, so then is this load_userids call
my @ids = grep { !$fom->{hide_watch_test}->($_) } @{ $fom->_mutually_watched_userids };
my $us = LJ::load_userids(@ids);
return $fom->{w_mut_users} = [
sort { $a->display_name cmp $b->display_name }
grep { $_->statusvis =~ /[VML]/ && $_->is_individual }
map { $us->{$_} ? ( $us->{$_} ) : () } @ids
];
}
# returns arrayref of mutually trusted userids. sorted by username
sub _mutually_trusted_userids {
my $fom = $_[0];
return $fom->{t_mut_userids} if $fom->{t_mut_userids};
confess 'attempted to get mutually trusted users with no input'
unless $fom->{t_userids};
my @mut;
foreach my $uid ( @{ $fom->_trusted_by_userids } ) {
push @mut, $uid if $fom->{t_userids}{$uid};
}
@mut = sort { $a <=> $b } @mut;
return $fom->{t_mut_userids} = \@mut;
}
# returns arrayref of mutually watched userids. sorted by username
sub _mutually_watched_userids {
my $fom = $_[0];
return $fom->{w_mut_userids} if $fom->{w_mut_userids};
confess 'attempted to get mutually watched users with no input'
unless $fom->{w_userids};
my @mut;
foreach my $uid ( @{ $fom->_watched_by_userids } ) {
push @mut, $uid if $fom->{w_userids}{$uid};
}
@mut = sort { $a <=> $b } @mut;
return $fom->{w_mut_userids} = \@mut;
}
# returns arrayref of inbound people/identity LJ::User objects, not communities. which means we gotta
# load them to filter, if it's not too much work. returns in sorted order.
sub _trusted_by_users {
my $fom = $_[0];
return $fom->{_trusted_by_users} if $fom->{_trusted_by_usercs};
# two options to filter them: a) it's less than load_cap, so we
# load all users and just look. b) it's too many, so we load at
# least the mutual friends + whatever's left in the load cap space
my @to_load;
my @uids = grep { !$fom->{hide_watch_test}->($_) } @{ $fom->_trusted_by_userids };
# remove mutuals now, if mutual separation has been required
if ( $fom->{mutualsep} ) {
@uids = grep { !$fom->{trusted_users}{$_} } @uids;
}
if ( @uids <= $fom->{load_cap} || !$fom->{sloppy} ) {
@to_load = @uids;
}
else {
# too big. we have to only load some. result will be limited.
# we'll always include mutual friends in our inbound load, unless we're
# separating them out anyway, in which case it's not important to make
# sure they're not forgotten, as they'll be included in the other list.
my %is_mutual;
unless ( $fom->{mutualsep} ) {
@to_load = @{ $fom->_mutually_trusted_userids };
$is_mutual{$_} = 1 foreach @to_load;
}
my $remain = $fom->{load_cap} - @to_load;
while ( $remain > 0 && @uids ) {
my $uid = shift @uids;
next if $is_mutual{$uid}; # already in mutual list
push @to_load, $uid;
$remain--;
}
$fom->{sloppy_load} = 1;
}
my $us = LJ::load_userids(@to_load);
return $fom->{_trusted_by_users} = [
sort { $a->display_name cmp $b->display_name }
grep { $_->statusvis =~ /[VML]/ && $_->is_individual }
map { $us->{$_} ? ( $us->{$_} ) : () } @to_load
];
}
1;