4 use vars qw( @ISA @EXPORT_OK $DEBUG $conf $jobnums);
7 use Storable qw( nfreeze thaw );
8 use FS::UID qw(myconnect);
10 use FS::Record qw( qsearch qsearchs dbh );
15 use FS::CGI qw(rooturl);
17 @ISA = qw(FS::Record);
18 @EXPORT_OK = qw( joblisting );
22 $FS::UID::callback{'FS::queue'} = sub {
30 FS::queue - Object methods for queue records
36 $record = new FS::queue \%hash;
37 $record = new FS::queue { 'column' => 'value' };
39 $error = $record->insert;
41 $error = $new_record->replace($old_record);
43 $error = $record->delete;
45 $error = $record->check;
49 An FS::queue object represents an queued job. FS::queue inherits from
50 FS::Record. The following fields are currently supported:
60 Fully-qualified subroutine name
64 Job status (new, locked, or failed)
68 Freeform text status message
74 if ( defined ( $_[0] ) ) {
75 $self->SUPER::statustext(@_);
77 my $value = $self->SUPER::statustext();
78 my $rooturl = rooturl();
79 $value =~ s/%%%ROOTURL%%%/$rooturl/g;
90 Optional link to service (see L<FS::cust_svc>).
94 Optional link to customer (see L<FS::cust_main>).
98 Secure flag, 'Y' indicates that when using encryption, the job needs to be
99 run on a machine with the private key.
111 Creates a new job. To add the job to the database, see L<"insert">.
113 Note that this stores the hash reference, not a distinct copy of the hash it
114 points to. You can ask the object for a copy with the I<hash> method.
118 # the new method can be inherited from FS::Record, if a table method is defined
120 sub table { 'queue'; }
122 =item insert [ ARGUMENT, ARGUMENT... ]
124 Adds this record to the database. If there is an error, returns the error,
125 otherwise returns false.
127 If any arguments are supplied, a queue_arg record for each argument is also
128 created (see L<FS::queue_arg>).
132 #false laziness w/part_export.pm
134 my( $self, @args ) = @_;
136 local $SIG{HUP} = 'IGNORE';
137 local $SIG{INT} = 'IGNORE';
138 local $SIG{QUIT} = 'IGNORE';
139 local $SIG{TERM} = 'IGNORE';
140 local $SIG{TSTP} = 'IGNORE';
141 local $SIG{PIPE} = 'IGNORE';
143 my $oldAutoCommit = $FS::UID::AutoCommit;
144 local $FS::UID::AutoCommit = 0;
153 $self->custnum( $args{'custnum'} ) if $args{'custnum'};
155 my $error = $self->SUPER::insert;
157 $dbh->rollback if $oldAutoCommit;
161 foreach my $arg ( @args ) {
162 my $freeze = ref($arg) ? 'Y' : '';
163 my $queue_arg = new FS::queue_arg ( {
164 'jobnum' => $self->jobnum,
166 'arg' => $freeze ? encode_base64(nfreeze($arg)) : $arg,# always freeze?
168 $error = $queue_arg->insert;
170 $dbh->rollback if $oldAutoCommit;
176 warn "jobnums global is active: $jobnums\n" if $DEBUG;
177 push @$jobnums, $self->jobnum;
180 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
188 Delete this record from the database. Any corresponding queue_arg records are
196 local $SIG{HUP} = 'IGNORE';
197 local $SIG{INT} = 'IGNORE';
198 local $SIG{QUIT} = 'IGNORE';
199 local $SIG{TERM} = 'IGNORE';
200 local $SIG{TSTP} = 'IGNORE';
201 local $SIG{PIPE} = 'IGNORE';
203 my $oldAutoCommit = $FS::UID::AutoCommit;
204 local $FS::UID::AutoCommit = 0;
207 my @del = qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } );
208 push @del, qsearch( 'queue_depend', { 'depend_jobnum' => $self->jobnum } );
211 if ( $self->status =~/^done/ ) {
212 my $dropstring = rooturl(). '/misc/queued_report\?report=';
213 if ($self->statustext =~ /.*$dropstring([.\w]+)\>/) {
214 $reportname = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/report.$1";
218 my $error = $self->SUPER::delete;
220 $dbh->rollback if $oldAutoCommit;
224 foreach my $del ( @del ) {
225 $error = $del->delete;
227 $dbh->rollback if $oldAutoCommit;
232 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
234 unlink $reportname if $reportname;
240 =item replace OLD_RECORD
242 Replaces the OLD_RECORD with this one in the database. If there is an error,
243 returns the error, otherwise returns false.
247 # the replace method can be inherited from FS::Record
251 Checks all fields to make sure this is a valid job. If there is
252 an error, returns the error, otherwise returns false. Called by the insert
260 $self->ut_numbern('jobnum')
261 || $self->ut_anything('job')
262 || $self->ut_numbern('_date')
263 || $self->ut_enum('status',['', qw( new locked failed done )])
264 || $self->ut_anything('statustext')
265 || $self->ut_numbern('svcnum')
267 return $error if $error;
269 $error = $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum');
270 $self->svcnum('') if $error;
272 $self->status('new') unless $self->status;
273 $self->_date(time) unless $self->_date;
280 Returns a list of the arguments associated with this job.
286 map { $_->frozen ? thaw(decode_base64($_->arg)) : $_->arg }
287 qsearch( 'queue_arg',
288 { 'jobnum' => $self->jobnum },
296 Returns the FS::cust_svc object associated with this job, if any.
302 qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
307 Returns the FS::queue_depend objects associated with this job, if any.
308 (Dependancies that must complete before this job can be run).
314 qsearch('queue_depend', { 'jobnum' => $self->jobnum } );
317 =item depend_insert OTHER_JOBNUM
319 Inserts a dependancy for this job - it will not be run until the other job
320 specified completes. If there is an error, returns the error, otherwise
323 When using job dependancies, you should wrap the insertion of all relevant jobs
324 in a database transaction.
329 my($self, $other_jobnum) = @_;
330 my $queue_depend = new FS::queue_depend ( {
331 'jobnum' => $self->jobnum,
332 'depend_jobnum' => $other_jobnum,
334 $queue_depend->insert;
339 Returns the FS::queue_depend objects that associate other jobs with this job,
340 if any. (The jobs that are waiting for this job to complete before they can
347 qsearch('queue_depend', { 'depend_jobnum' => $self->jobnum } );
350 =item depended_delete
352 Deletes the other queued jobs (FS::queue objects) that are waiting for this
353 job, if any. If there is an error, returns the error, otherwise returns false.
357 sub depended_delete {
361 map { qsearchs('queue', { 'jobnum' => $_->jobnum } ) } $self->queue_depended
363 $error = $job->depended_delete;
364 return $error if $error;
365 $error = $job->delete;
366 return $error if $error
370 =item update_statustext VALUE
372 Updates the statustext value of this job to supplied value, in the database.
373 If there is an error, returns the error, otherwise returns false.
377 use vars qw($_update_statustext_dbh);
378 sub update_statustext {
379 my( $self, $statustext ) = @_;
380 return '' if $statustext eq $self->get('statustext'); #avoid rooturl expansion
381 warn "updating statustext for $self to $statustext" if $DEBUG;
383 $_update_statustext_dbh ||= myconnect;
385 my $sth = $_update_statustext_dbh->prepare(
386 'UPDATE queue set statustext = ? WHERE jobnum = ?'
387 ) or return $_update_statustext_dbh->errstr;
389 $sth->execute($statustext, $self->jobnum) or return $sth->errstr;
390 $_update_statustext_dbh->commit or die $_update_statustext_dbh->errstr;
391 $self->set('statustext', $statustext); #avoid rooturl expansion
394 #my $new = new FS::queue { $self->hash };
395 #$new->statustext($statustext);
396 #my $error = $new->replace($self);
397 #return $error if $error;
398 #$self->statustext($statustext);
408 =item joblisting HASHREF NOACTIONS
413 my($hashref, $noactions) = @_;
419 my @queue = qsearch( 'queue', $hashref );
420 return '' unless scalar(@queue);
422 my $p = FS::CGI::popurl(2);
424 my $html = qq!<FORM ACTION="$p/misc/queue.cgi" METHOD="POST">!.
425 FS::CGI::table(). <<END;
427 <TH COLSPAN=2>Job</TH>
432 $html .= '<TH>Account</TH>' unless $hashref->{svcnum};
435 my $dangerous = $conf->exists('queue_dangerous_controls');
439 foreach my $queue ( sort {
440 $a->getfield('jobnum') <=> $b->getfield('jobnum')
442 my $queue_hashref = $queue->hashref;
443 my $jobnum = $queue->jobnum;
446 if ( $dangerous || $queue->job !~ /^FS::part_export::/ || !$noactions ) {
447 $args = encode_entities( join(' ', $queue->args) );
452 my $date = time2str( "%a %b %e %T %Y", $queue->_date );
453 my $status = $queue->status;
454 $status .= ': '. $queue->statustext if $queue->statustext;
455 my @queue_depend = $queue->queue_depend;
456 $status .= ' (waiting for '.
457 join(', ', map { $_->depend_jobnum } @queue_depend ).
460 my $changable = $dangerous
461 || ( ! $noactions && $status =~ /^failed/ || $status =~ /^locked/ );
464 qq! ( <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A> |!.
465 qq! <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A> )!;
467 my $cust_svc = $queue->cust_svc;
472 <TD>$queue_hashref->{job}</TD>
478 unless ( $hashref->{svcnum} ) {
481 my $table = $cust_svc->part_svc->svcdb;
482 my $label = ( $cust_svc->label )[1];
483 $account = qq!<A HREF="../view/$table.cgi?!. $queue->svcnum.
488 $html .= "<TD>$account</TD>";
494 qq!<TD><INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1"></TD>!;
505 $html .= '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'.
506 '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>';
521 L<FS::Record>, schema.html from the base documentation.