172 lines
5 KiB
Perl
Executable file
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();
|
|
}
|