mourningdove/bin/worker/birthday-notify
2026-05-24 01:03:05 +00:00

172 lines
5 KiB
Perl
Executable file

#!/usr/bin/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::Worker::BirthdayNotify;
use strict;
BEGIN {
require "$ENV{LJHOME}/cgi-bin/ljlib.pl";
}
use base 'LJ::Worker::Manual';
use LJ::Event::Birthday;
use List::Util ();
# send out for notifications up to two days in the future
my $advance_notify = $LJ::BIRTHDAY_NOTIFS_ADVANCE || 2*86400; # 2 days
# delay for polling clusters that last had no users
my $delay_when_none = 15;
# how long to wait if we didn't process any at all
my $sleep_when_idle = 10;
# mapping of clusterid -> when they were last empty
my %last_empty;
# the cluster we're working on. used in several places.
my $working_clusterid;
sub work {
my $class = shift;
my @uids = @_;
# pick a cluster to work on, get a lock
my $lock;
foreach my $cid (List::Util::shuffle(@LJ::CLUSTERS)) {
last if @uids;
debug("Checking cluster: $cid");
next unless cluster_needs_work($cid);
$lock = LJ::locker()->trylock("birthday-notify:" . $cid);
next unless $lock;
# got a lock and a reader
debug("Fetching users from cluster: $cid");
push @uids, get_users_from_cluster($cid);
# found a clusterid to work on!
$working_clusterid = $cid;
last;
}
# when we return 0 here, we'll be considered idle
return 0 unless @uids;
my $us = LJ::load_userids(@uids);
my $ct = 0;
# a multiloader to fetch everyone's birthdays currently, so
# we can discard notifications for people whose birthdays
# have already passed (eg, worker backlog)
my $bdays = LJ::User->next_birthdays(@uids);
foreach my $u (values %$us) {
# don't want to process read-only users (being moved?)
next if $u->readonly;
# if a user's data is still on this cluster, but the user isn't,
# we want to enter lazy-cleanup mode so we don't spin on these users
# (this is a superset of the case of expunged users)
if ($u->clusterid != $working_clusterid) {
remove_user_from_cluster($u, $working_clusterid);
next;
}
# do this first, so we properly update even if we don't notify
# next out if the setter failed (eg, invalid birthday)
$u->set_next_birthday or next;
# don't mail if their birthday's already passed
next if $bdays->{$u->id} < time();
next unless $u->should_fire_birthday_notif;
debug("Firing off notification for " . $u->user);
LJ::Event::Birthday->new($u)->fire;
$ct++;
}
# return and release our lock as it falls out of scope
return $ct;
}
sub on_idle {
return sleep $sleep_when_idle;
}
sub debug {
LJ::Worker::Manual->cond_debug(@_);
}
# checks if a cluster has pending notifications to send out.
sub cluster_needs_work {
my $cluster = shift;
# don't hammer a cluster if we don't have anyone on it.
return if $last_empty{$cluster} && $delay_when_none > time() - $last_empty{$cluster};
# now check if there are pending notifications on the cluster
my $dbcr = LJ::get_cluster_def_reader($cluster);
return unless $dbcr;
my $ct = $dbcr->selectrow_array("SELECT userid FROM birthdays WHERE " .
"nextbirthday < UNIX_TIMESTAMP() + $advance_notify LIMIT 1")+0;
die $dbcr->errstr if $dbcr->err;
return 1 if $ct;
# otherwise, we had nobody. make a note of that.
$last_empty{$cluster} = time();
return undef;
}
sub get_users_from_cluster {
my $cluster = shift;
my $dbcr = LJ::get_cluster_def_reader($cluster);
die "Unable to get cluster reader for cluster $cluster" unless $dbcr;
my $userids = $dbcr->selectcol_arrayref("SELECT userid FROM birthdays " .
"WHERE nextbirthday < UNIX_TIMESTAMP() + $advance_notify LIMIT 1000");
die $dbcr->errstr if $dbcr->err;
return @$userids;
}
sub remove_user_from_cluster {
my ($u, $cid) = @_;
debug("Cleaning up for moved user: " . $u->user);
my $dbh = LJ::get_cluster_master($cid)
or die "Unable to get cluster reader for cluster: $cid";
$dbh->do("DELETE FROM birthdays WHERE userid = ?", undef, $u->id);
die $dbh->errstr if $dbh->err;
}
################################################################################
# to be able to work on one specific user
if (@ARGV) {
my $user = shift;
my $u = LJ::load_user($user);
$working_clusterid = $u->clusterid;
__PACKAGE__->work($u->id);
} else {
__PACKAGE__->run();
}