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
76 Optional link to service (see L<FS::cust_svc>).
80 Optional link to customer (see L<FS::cust_main>).
84 Secure flag, 'Y' indicates that when using encryption, the job needs to be
85 run on a machine with the private key.
97 Creates a new job. To add the job to the database, see L<"insert">.
99 Note that this stores the hash reference, not a distinct copy of the hash it
100 points to. You can ask the object for a copy with the I<hash> method.
104 # the new method can be inherited from FS::Record, if a table method is defined
106 sub table { 'queue'; }
108 =item insert [ ARGUMENT, ARGUMENT... ]
110 Adds this record to the database. If there is an error, returns the error,
111 otherwise returns false.
113 If any arguments are supplied, a queue_arg record for each argument is also
114 created (see L<FS::queue_arg>).
118 #false laziness w/part_export.pm
120 my( $self, @args ) = @_;
122 local $SIG{HUP} = 'IGNORE';
123 local $SIG{INT} = 'IGNORE';
124 local $SIG{QUIT} = 'IGNORE';
125 local $SIG{TERM} = 'IGNORE';
126 local $SIG{TSTP} = 'IGNORE';
127 local $SIG{PIPE} = 'IGNORE';
129 my $oldAutoCommit = $FS::UID::AutoCommit;
130 local $FS::UID::AutoCommit = 0;
139 $self->custnum( $args{'custnum'} ) if $args{'custnum'};
141 my $error = $self->SUPER::insert;
143 $dbh->rollback if $oldAutoCommit;
147 foreach my $arg ( @args ) {
148 my $freeze = ref($arg) ? 'Y' : '';
149 my $queue_arg = new FS::queue_arg ( {
150 'jobnum' => $self->jobnum,
152 'arg' => $freeze ? encode_base64(nfreeze($arg)) : $arg,# always freeze?
154 $error = $queue_arg->insert;
156 $dbh->rollback if $oldAutoCommit;
162 warn "jobnums global is active: $jobnums\n" if $DEBUG;
163 push @$jobnums, $self->jobnum;
166 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
174 Delete this record from the database. Any corresponding queue_arg records are
182 local $SIG{HUP} = 'IGNORE';
183 local $SIG{INT} = 'IGNORE';
184 local $SIG{QUIT} = 'IGNORE';
185 local $SIG{TERM} = 'IGNORE';
186 local $SIG{TSTP} = 'IGNORE';
187 local $SIG{PIPE} = 'IGNORE';
189 my $oldAutoCommit = $FS::UID::AutoCommit;
190 local $FS::UID::AutoCommit = 0;
193 my @del = qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } );
194 push @del, qsearch( 'queue_depend', { 'depend_jobnum' => $self->jobnum } );
197 if ( $self->status =~/^done/ ) {
198 my $dropstring = rooturl(). '/misc/queued_report\?report=';
199 if ($self->statustext =~ /.*$dropstring([.\w]+)\>/) {
200 $reportname = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/report.$1";
204 my $error = $self->SUPER::delete;
206 $dbh->rollback if $oldAutoCommit;
210 foreach my $del ( @del ) {
211 $error = $del->delete;
213 $dbh->rollback if $oldAutoCommit;
218 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
220 unlink $reportname if $reportname;
226 =item replace OLD_RECORD
228 Replaces the OLD_RECORD with this one in the database. If there is an error,
229 returns the error, otherwise returns false.
233 # the replace method can be inherited from FS::Record
237 Checks all fields to make sure this is a valid job. If there is
238 an error, returns the error, otherwise returns false. Called by the insert
246 $self->ut_numbern('jobnum')
247 || $self->ut_anything('job')
248 || $self->ut_numbern('_date')
249 || $self->ut_enum('status',['', qw( new locked failed done )])
250 || $self->ut_anything('statustext')
251 || $self->ut_numbern('svcnum')
253 return $error if $error;
255 $error = $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum');
256 $self->svcnum('') if $error;
258 $self->status('new') unless $self->status;
259 $self->_date(time) unless $self->_date;
266 Returns a list of the arguments associated with this job.
272 map { $_->frozen ? thaw(decode_base64($_->arg)) : $_->arg }
273 qsearch( 'queue_arg',
274 { 'jobnum' => $self->jobnum },
282 Returns the FS::cust_svc object associated with this job, if any.
288 qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
293 Returns the FS::queue_depend objects associated with this job, if any.
294 (Dependancies that must complete before this job can be run).
300 qsearch('queue_depend', { 'jobnum' => $self->jobnum } );
303 =item depend_insert OTHER_JOBNUM
305 Inserts a dependancy for this job - it will not be run until the other job
306 specified completes. If there is an error, returns the error, otherwise
309 When using job dependancies, you should wrap the insertion of all relevant jobs
310 in a database transaction.
315 my($self, $other_jobnum) = @_;
316 my $queue_depend = new FS::queue_depend ( {
317 'jobnum' => $self->jobnum,
318 'depend_jobnum' => $other_jobnum,
320 $queue_depend->insert;
325 Returns the FS::queue_depend objects that associate other jobs with this job,
326 if any. (The jobs that are waiting for this job to complete before they can
333 qsearch('queue_depend', { 'depend_jobnum' => $self->jobnum } );
336 =item depended_delete
338 Deletes the other queued jobs (FS::queue objects) that are waiting for this
339 job, if any. If there is an error, returns the error, otherwise returns false.
343 sub depended_delete {
347 map { qsearchs('queue', { 'jobnum' => $_->jobnum } ) } $self->queue_depended
349 $error = $job->depended_delete;
350 return $error if $error;
351 $error = $job->delete;
352 return $error if $error
356 =item update_statustext VALUE
358 Updates the statustext value of this job to supplied value, in the database.
359 If there is an error, returns the error, otherwise returns false.
363 use vars qw($_update_statustext_dbh);
364 sub update_statustext {
365 my( $self, $statustext ) = @_;
366 return '' if $statustext eq $self->statustext;
367 warn "updating statustext for $self to $statustext" if $DEBUG;
369 $_update_statustext_dbh ||= myconnect;
371 my $sth = $_update_statustext_dbh->prepare(
372 'UPDATE queue set statustext = ? WHERE jobnum = ?'
373 ) or return $_update_statustext_dbh->errstr;
375 $sth->execute($statustext, $self->jobnum) or return $sth->errstr;
376 $_update_statustext_dbh->commit or die $_update_statustext_dbh->errstr;
377 $self->statustext($statustext);
380 #my $new = new FS::queue { $self->hash };
381 #$new->statustext($statustext);
382 #my $error = $new->replace($self);
383 #return $error if $error;
384 #$self->statustext($statustext);
394 =item joblisting HASHREF NOACTIONS
399 my($hashref, $noactions) = @_;
405 my @queue = qsearch( 'queue', $hashref );
406 return '' unless scalar(@queue);
408 my $p = FS::CGI::popurl(2);
410 my $html = qq!<FORM ACTION="$p/misc/queue.cgi" METHOD="POST">!.
411 FS::CGI::table(). <<END;
413 <TH COLSPAN=2>Job</TH>
418 $html .= '<TH>Account</TH>' unless $hashref->{svcnum};
421 my $dangerous = $conf->exists('queue_dangerous_controls');
425 foreach my $queue ( sort {
426 $a->getfield('jobnum') <=> $b->getfield('jobnum')
428 my $queue_hashref = $queue->hashref;
429 my $jobnum = $queue->jobnum;
432 if ( $dangerous || $queue->job !~ /^FS::part_export::/ || !$noactions ) {
433 $args = encode_entities( join(' ', $queue->args) );
438 my $date = time2str( "%a %b %e %T %Y", $queue->_date );
439 my $status = $queue->status;
440 $status .= ': '. $queue->statustext if $queue->statustext;
441 my @queue_depend = $queue->queue_depend;
442 $status .= ' (waiting for '.
443 join(', ', map { $_->depend_jobnum } @queue_depend ).
446 my $changable = $dangerous
447 || ( ! $noactions && $status =~ /^failed/ || $status =~ /^locked/ );
450 qq! ( <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A> |!.
451 qq! <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A> )!;
453 my $cust_svc = $queue->cust_svc;
458 <TD>$queue_hashref->{job}</TD>
464 unless ( $hashref->{svcnum} ) {
467 my $table = $cust_svc->part_svc->svcdb;
468 my $label = ( $cust_svc->label )[1];
469 $account = qq!<A HREF="../view/$table.cgi?!. $queue->svcnum.
474 $html .= "<TD>$account</TD>";
480 qq!<TD><INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1"></TD>!;
491 $html .= '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'.
492 '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>';
507 L<FS::Record>, schema.html from the base documentation.