summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
authorivan <ivan>2001-09-11 00:08:18 +0000
committerivan <ivan>2001-09-11 00:08:18 +0000
commitf5266a4d07d116efd732f433d0f4f3a47b143a7d (patch)
tree30b5bd95a9d3b3c588ab097f1877ae0d27e96741 /FS
parent85e59606c0b5eed9780534ffaf554aa32bcf9baf (diff)
faster (cached) fuzzy searches
prelim. job queues! fixed part_svc editing
Diffstat (limited to 'FS')
-rw-r--r--FS/FS.pm8
-rw-r--r--FS/FS/Record.pm21
-rw-r--r--FS/FS/cust_credit_bill.pm10
-rw-r--r--FS/FS/cust_main.pm143
-rw-r--r--FS/FS/part_svc.pm63
-rw-r--r--FS/FS/queue.pm174
-rw-r--r--FS/FS/queue_arg.pm121
-rw-r--r--FS/MANIFEST5
-rwxr-xr-xFS/bin/freeside-bill4
-rw-r--r--FS/bin/freeside-queued145
-rw-r--r--FS/t/queue.t5
-rw-r--r--FS/t/queue_arg.t5
12 files changed, 687 insertions, 17 deletions
diff --git a/FS/FS.pm b/FS/FS.pm
index cd11e96..d2f47d7 100644
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -93,7 +93,7 @@ L<FS::cust_refund> - Refund class
L<FS::cust_credit_refund> - Refund application class
-L<FS::cust_credit_bill> - Refund invoice application class
+L<FS::cust_credit_bill> - Credit invoice application class
L<FS::cust_pay_batch> - Credit card transaction queue class
@@ -105,6 +105,10 @@ L<FS::port> - NAS port class
L<FS::session> - User login session class
+L<FS::queue> - Job queue
+
+L<FS::queue_arg> - Job arguments
+
=head2 User Interface classes (under development; not yet usable)
L<FS::UI::Base> - User-interface base class
@@ -135,7 +139,7 @@ The main documentation is in htdocs/docs.
=head1 VERSION
-$Id: FS.pm,v 1.7 2001-09-06 20:41:59 ivan Exp $
+$Id: FS.pm,v 1.8 2001-09-11 00:08:18 ivan Exp $
=head1 SUPPORT
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
index 9a532e3..b476df2 100644
--- a/FS/FS/Record.pm
+++ b/FS/FS/Record.pm
@@ -855,13 +855,30 @@ Untaints arbitrary data. Be careful.
=cut
sub ut_anything {
- my($self,$field)=@_;
+ my( $self, $field ) = @_;
$self->getfield($field) =~ /^(.*)$/s
or return "Illegal $field: ". $self->getfield($field);
$self->setfield($field,$1);
'';
}
+=item ut_enum COLUMN CHOICES_ARRAYREF
+
+Check/untaint a column, supplying all possible choices, like the "enum" type.
+
+=cut
+
+sub ut_enum {
+ my( $self, $field, $choices ) = @_;
+ foreach my $choice ( @$choices ) {
+ if ( $self->getfield($field) eq $choice ) {
+ $self->setfield($choice);
+ return '';
+ }
+ }
+ return "Illegal (enum) field $field: ". $self->getfield($field);
+}
+
=item fields [ TABLE ]
This can be used as both a subroutine and a method call. It returns a list
@@ -976,7 +993,7 @@ sub DESTROY { return; }
=head1 VERSION
-$Id: Record.pm,v 1.26 2001-08-31 09:20:35 ivan Exp $
+$Id: Record.pm,v 1.27 2001-09-11 00:08:18 ivan Exp $
=head1 BUGS
diff --git a/FS/FS/cust_credit_bill.pm b/FS/FS/cust_credit_bill.pm
index 58cc789..9613408 100644
--- a/FS/FS/cust_credit_bill.pm
+++ b/FS/FS/cust_credit_bill.pm
@@ -33,12 +33,14 @@ FS::cust_credit_bill - Object methods for cust_credit_bill records
=head1 DESCRIPTION
An FS::cust_credit_bill object represents application of a credit (see
-L<FS::cust_credit>) to a customer bill (see L<FS::cust_bill>). FS::cust_credit
+L<FS::cust_credit>) to an invoice (see L<FS::cust_bill>). FS::cust_credit
inherits from FS::Record. The following fields are currently supported:
=over 4
-=item crednum - primary key; credit being applied
+=item creditbillnum - primary key
+
+=item crednum - credit being applied
=item invnum - invoice to which credit is applied (see L<FS::cust_bill>)
@@ -119,7 +121,7 @@ sub check {
$self->_date(time) unless $self->_date;
- return "Cannot apply more than remaining value of credit memo"
+ return "Cannot apply more than remaining value of credit"
unless $self->amount <= $cust_credit->credited;
return "Cannot apply more than remaining value of invoice"
@@ -143,7 +145,7 @@ sub cust_credit {
=head1 VERSION
-$Id: cust_credit_bill.pm,v 1.4 2001-09-02 07:49:52 ivan Exp $
+$Id: cust_credit_bill.pm,v 1.5 2001-09-11 00:08:18 ivan Exp $
=head1 BUGS
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index b29e385..e4f55cf 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -31,6 +31,7 @@ use FS::cust_main_invoice;
use FS::cust_credit_bill;
use FS::cust_bill_pay;
use FS::prepay_credit;
+use FS::queue;
@ISA = qw( FS::Record );
@@ -343,6 +344,22 @@ sub insert {
}
}
+ my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' };
+ $error = $queue->insert($self->getfield('last'), $self->company);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+
+ if ( defined $self->dbdef_table->column('ship_last') && $self->ship_last ) {
+ $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' };
+ $error = $queue->insert($self->getfield('last'), $self->company);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+ }
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
@@ -1533,24 +1550,140 @@ sub referral_cust_main {
=over 4
-=item rebuild_fuzzyfile
+=item check_and_rebuild_fuzzyfiles
+
+=cut
+
+sub check_and_rebuild_fuzzyfiles {
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ -e "$dir/cust_main.last" && -e "$dir/cust_main.company"
+ or &rebuild_fuzzyfiles;
+}
+
+=item rebuild_fuzzyfiles
=cut
sub rebuild_fuzzyfiles {
+
+ use Fcntl qw(:flock);
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+
+ #last
+
+ open(LASTLOCK,">>$dir/cust_main.last")
+ or die "can't open $dir/cust_main.last: $!";
+ flock(LASTLOCK,LOCK_EX)
+ or die "can't lock $dir/cust_main.last: $!";
+
my @all_last = map $_->getfield('last'), qsearch('cust_main', {});
push @all_last,
grep $_, map $_->getfield('ship_last'), qsearch('cust_main',{})
- if defined dbdef->table('cust_main')->column('ship_last');
-# open(
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ open (LASTCACHE,">$dir/cust_main.last.tmp")
+ or die "can't open $dir/cust_main.last.tmp: $!";
+ print LASTCACHE join("\n", @all_last), "\n";
+ close LASTCACHE or die "can't close $dir/cust_main.last.tmp: $!";
+
+ rename "$dir/cust_main.last.tmp", "$dir/cust_main.last";
+ close LASTLOCK;
+
+ #company
+
+ open(COMPANYLOCK,">>$dir/cust_main.company")
+ or die "can't open $dir/cust_main.company: $!";
+ flock(COMPANYLOCK,LOCK_EX)
+ or die "can't lock $dir/cust_main.company: $!";
+
+ my @all_company = grep $_ ne '', map $_->company, qsearch('cust_main',{});
+ push @all_company,
+ grep $_ ne '', map $_->ship_company, qsearch('cust_main', {})
+ if defined dbdef->table('cust_main')->column('ship_last');
+
+ open (COMPANYCACHE,">$dir/cust_main.company.tmp")
+ or die "can't open $dir/cust_main.company.tmp: $!";
+ print COMPANYCACHE join("\n", @all_company), "\n";
+ close COMPANYCACHE or die "can't close $dir/cust_main.company.tmp: $!";
+
+ rename "$dir/cust_main.company.tmp", "$dir/cust_main.company";
+ close COMPANYLOCK;
}
-=back
+=item all_last
+
+=cut
+
+sub all_last {
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ open(LASTCACHE,"<$dir/cust_main.last")
+ or die "can't open $dir/cust_main.last: $!";
+ my @array = split(/\n/, <LASTCACHE> );
+ close LASTCACHE;
+ \@array;
+}
+
+=item all_company
+
+=cut
+
+sub all_company {
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+ open(COMPANYCACHE,"<$dir/cust_main.company")
+ or die "can't open $dir/cust_main.last: $!";
+ my @array = split(/\n/, <COMPANYCACHE> );
+ close COMPANYCACHE;
+ \@array;
+}
+
+=item append_fuzzyfiles LASTNAME COMPANY
+
+=cut
+
+sub append_fuzzyfiles {
+ my( $last, $company ) = @_;
+
+ use Fcntl qw(:flock);
+
+ my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc;
+
+ if ( $last ) {
+
+ open(LAST,">>$dir/cust_main.last")
+ or die "can't open $dir/cust_main.last: $!";
+ flock(LAST,LOCK_EX)
+ or die "can't lock $dir/cust_main.last: $!";
+
+ print LAST "$last\n";
+
+ flock(LAST,LOCK_UN)
+ or die "can't unlock $dir/cust_main.last: $!";
+ close LAST;
+ }
+
+ if ( $company ) {
+
+ open(COMPANY,">>$dir/cust_main.company")
+ or die "can't open $dir/cust_main.company: $!";
+ flock(COMPANY,LOCK_EX)
+ or die "can't lock $dir/cust_main.company: $!";
+
+ print COMPANY "$company\n";
+
+ flock(COMPANY,LOCK_UN)
+ or die "can't unlock $dir/cust_main.company: $!";
+
+ close COMPANY;
+ }
+
+ 1;
+}
=head1 VERSION
-$Id: cust_main.pm,v 1.29 2001-09-03 22:07:38 ivan Exp $
+$Id: cust_main.pm,v 1.30 2001-09-11 00:08:18 ivan Exp $
=head1 BUGS
diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm
index e64f09a..f1e71ad 100644
--- a/FS/FS/part_svc.pm
+++ b/FS/FS/part_svc.pm
@@ -117,7 +117,7 @@ sub insert {
$error = $part_svc_column->insert;
}
} else {
- $error = $part_svc_column->delete;
+ $error = $previous ? $previous->delete : '';
}
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
@@ -155,7 +155,64 @@ sub replace {
return "Can't change svcdb for an existing service definition!"
unless $old->svcdb eq $new->svcdb;
- $new->SUPER::replace( $old );
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $new->SUPER::replace( $old );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( @_ && $_[0] eq '1.3-COMPAT' ) {
+ my $svcdb = $new->svcdb;
+ foreach my $field (
+ grep { $_ ne 'svcnum'
+ && defined( $new->getfield($svcdb.'__'.$_.'_flag') )
+ } fields($svcdb)
+ ) {
+ my $part_svc_column = $new->part_svc_column($field);
+ my $previous = qsearchs('part_svc_column', {
+ 'svcpart' => $new->svcpart,
+ 'columnname' => $field,
+ } );
+
+ my $flag = $new->getfield($svcdb.'__'.$field.'_flag');
+ if ( uc($flag) =~ /^([DF])$/ ) {
+ $part_svc_column->setfield('columnflag', $1);
+ $part_svc_column->setfield('columnvalue',
+ $new->getfield($svcdb.'__'.$field)
+ );
+ if ( $previous ) {
+ $error = $part_svc_column->replace($previous);
+ } else {
+ $error = $part_svc_column->insert;
+ }
+ } else {
+ $error = $previous ? $previous->delete : '';
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ } else {
+ $dbh->rollback if $oldAutoCommit;
+ return 'non-1.3-COMPAT interface not yet written';
+ #not yet implemented
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
}
=item check
@@ -232,7 +289,7 @@ sub part_svc_column {
=head1 VERSION
-$Id: part_svc.pm,v 1.3 2001-09-06 20:41:59 ivan Exp $
+$Id: part_svc.pm,v 1.4 2001-09-11 00:08:18 ivan Exp $
=head1 BUGS
diff --git a/FS/FS/queue.pm b/FS/FS/queue.pm
new file mode 100644
index 0000000..51dd9af
--- /dev/null
+++ b/FS/FS/queue.pm
@@ -0,0 +1,174 @@
+package FS::queue;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::queue_arg;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::queue - Object methods for queue records
+
+=head1 SYNOPSIS
+
+ use FS::queue;
+
+ $record = new FS::queue \%hash;
+ $record = new FS::queue { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::queue object represents an queued job. FS::queue inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item jobnum - primary key
+
+=item job - fully-qualified subroutine name
+
+=item status - job status
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new job. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'queue'; }
+
+=item insert [ ARGUMENT, ARGUMENT... ]
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+If any arguments are supplied, a queue_arg record for each argument is also
+created (see L<FS::queue_arg>).
+
+=cut
+
+sub insert {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ foreach my $arg ( @_ ) {
+ my $queue_arg = new FS::queue_arg ( {
+ 'jobnum' => $self->jobnum,
+ 'arg' => $arg,
+ } );
+ $error = $queue_arg->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid job. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+ my $error =
+ $self->ut_numbern('jobnum')
+ || $self->ut_anything('job')
+ || $self->ut_numbern('_date')
+ || $self->ut_enum('status',['', qw( new locked failed )])
+ ;
+ return $error if $error;
+
+ $self->status('new') unless $self->status;
+ $self->_date(time) unless $self->_date;
+
+ ''; #no error
+}
+
+=item args
+
+=cut
+
+sub args {
+ my $self = shift;
+ map $_->arg, qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } );
+}
+
+=back
+
+=head1 VERSION
+
+$Id: queue.pm,v 1.1 2001-09-11 00:08:18 ivan Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/queue_arg.pm b/FS/FS/queue_arg.pm
new file mode 100644
index 0000000..08fe473
--- /dev/null
+++ b/FS/FS/queue_arg.pm
@@ -0,0 +1,121 @@
+package FS::queue_arg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::queue_arg - Object methods for queue_arg records
+
+=head1 SYNOPSIS
+
+ use FS::queue_arg;
+
+ $record = new FS::queue_arg \%hash;
+ $record = new FS::queue_arg { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::queue_arg object represents job argument. FS::queue_arg inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item argnum - primary key
+
+=item jobnum - see L<FS::queue>
+
+=item arg - argument
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new argument. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'queue_arg'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid argument. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+ my $error =
+ $self->ut_numbern('argnum')
+ || $self->ut_numbern('jobnum')
+ || $self->ut_anything('arg')
+ ;
+ return $error if $error;
+
+ ''; #no error
+}
+
+=back
+
+=head1 VERSION
+
+$Id: queue_arg.pm,v 1.1 2001-09-11 00:08:18 ivan Exp $
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::queue>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index ae3e0b0..69123db 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -6,6 +6,7 @@ README
bin/freeside-bill
bin/freeside-email
bin/freeside-print-batch
+bin/freeside-queued
FS.pm
FS/CGI.pm
FS/Conf.pm
@@ -51,6 +52,8 @@ FS/svc_www.pm
FS/CGIwrapper.pm
FS/svc_forward.pm
FS/raddb.pm
+FS/queue.pm
+FS/queue_arg.pm
t/agent.t
t/agent_type.t
t/CGI.t
@@ -89,5 +92,7 @@ t/svc_domain.t
t/svc_forward.t
t/svc_www.t
t/type_pkgs.t
+t/queue.t
+t/queue_arg.t
t/UID.t
t/raddb.t
diff --git a/FS/bin/freeside-bill b/FS/bin/freeside-bill
index 1dce9a1..82b3321 100755
--- a/FS/bin/freeside-bill
+++ b/FS/bin/freeside-bill
@@ -1,3 +1,5 @@
+#!/usr/bin/perl -w
+# don't take any world-facing input
#!/usr/bin/perl -Tw
use strict;
@@ -121,7 +123,7 @@ customers. Otherwise, bills all customers.
=head1 VERSION
-$Id: freeside-bill,v 1.8 2001-09-03 22:07:39 ivan Exp $
+$Id: freeside-bill,v 1.9 2001-09-11 00:08:18 ivan Exp $
=head1 BUGS
diff --git a/FS/bin/freeside-queued b/FS/bin/freeside-queued
new file mode 100644
index 0000000..5acffb5
--- /dev/null
+++ b/FS/bin/freeside-queued
@@ -0,0 +1,145 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Fcntl qw(:flock);
+use POSIX qw(setsid);
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearchs);
+use FS::queue;
+
+# no autoloading just yet
+use FS::cust_main;
+
+my $pid_file = '/var/run/freeside-queued.pid';
+
+$SIG{CHLD} = sub { wait }; #zombie prevention
+
+my $sigterm = 0;
+my $sigint = 0;
+$SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $sigint++; };
+$SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $sigterm++; };
+
+my $user = shift or die &usage;
+
+&daemonize;
+
+my $log_file = "/usr/local/etc/freeside/queuelog.";
+
+$> = $FS::UID::freeside_uid unless $>;
+adminsuidsetup $user;
+
+$log_file = "/usr/local/etc/freeside/queuelog.". $FS::UID::datasrc;
+
+$SIG{__DIE__} = \&_die;
+$SIG{__WARN__} = \&_logmsg;
+
+
+while (1) {
+
+ my $job = qsearchs(
+ 'queue',
+ { 'status' => 'new' },
+ '',
+ 'ORDER BY jobnum FOR UPDATE LIMIT 1'
+ ) or do {
+ sleep 5;
+ next;
+ };
+
+ my %hash = $job->hash;
+ $hash{'status'} = 'locked';
+ my $ljob = new FS::queue ( \%hash );
+ my $error = $ljob->replace($job);
+ die $error if $error;
+
+ my @args = $ljob->args;
+
+ #fork a child for each job (up to some maximum perhaps?)
+ #single-threaded for now.
+
+ my $eval = "&". $ljob->job. '(@args);';
+ warn "running $eval";
+ eval $eval;
+ if ( $@ ) {
+ warn "job $eval failed";
+ my $hash = $ljob->hash;
+ $hash{'status'} = 'failed';
+ my $fjob = new FS::queue( \%hash );
+ my $error = $fjob->replace($ljob);
+ die $error if $error;
+ } else {
+ $ljob->delete;
+ }
+
+} continue {
+ if ( $sigterm ) {
+ warn "received TERM signal; exiting\n";
+ exit;
+ }
+ if ( $sigint ) {
+ warn "received INT signal; exiting\n";
+ exit;
+ }
+}
+
+
+sub datestamp {
+ time2str("%m%d%Y", time);
+}
+
+sub _die {
+ my $msg = shift;
+ unlink $pid_file if -e $pid_file;
+ _logmsg($msg);
+}
+
+sub _logmsg {
+ chomp( my $msg = shift );
+ my $log = new IO::File ">>$log_file";
+ flock($log, LOCK_EX);
+ seek($log, 0, 2);
+ print $log "[". time2str("%a %b %e %T %Y",time). "] [$$] $msg\n";
+ flock($log, LOCK_UN);
+}
+
+sub daemonize {
+
+ chdir "/" or die "Can't chdir to /: $!";
+ open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
+ defined(my $pid = fork) or die "Can't fork: $!";
+ if ( $pid ) {
+ print "freeside-queued started with pid $pid\n"; #logging to $log_file\n";
+ exit unless $pid_file;
+ my $pidfh = new IO::File ">$pid_file" or exit;
+ print $pidfh "$pid\n";
+ exit;
+ }
+ open STDOUT, '>/dev/null'
+ or die "Can't write to /dev/null: $!";
+ setsid or die "Can't start a new session: $!";
+ open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
+
+}
+
+=head1 NAME
+
+freeside-queued - Job queue daemon
+
+=head1 SYNOPSIS
+
+ freeside-queued user
+
+=head1 DESCRIPTION
+
+Job queue daemon. Should be running at all times.
+
+user: from the mapsecrets file - see config.html from the base documentation
+
+=head1 VERSION
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+=cut
+
diff --git a/FS/t/queue.t b/FS/t/queue.t
new file mode 100644
index 0000000..43e3373
--- /dev/null
+++ b/FS/t/queue.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::queue;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/queue_arg.t b/FS/t/queue_arg.t
new file mode 100644
index 0000000..cf3f91d
--- /dev/null
+++ b/FS/t/queue_arg.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::queue_arg;
+$loaded=1;
+print "ok 1\n";