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.
103 For access_user that created the job
115 Creates a new job. To add the job to the database, see L<"insert">.
117 Note that this stores the hash reference, not a distinct copy of the hash it
118 points to. You can ask the object for a copy with the I<hash> method.
122 # the new method can be inherited from FS::Record, if a table method is defined
124 sub table { 'queue'; }
126 =item insert [ ARGUMENT, ARGUMENT... ]
128 Adds this record to the database. If there is an error, returns the error,
129 otherwise returns false.
131 If any arguments are supplied, a queue_arg record for each argument is also
132 created (see L<FS::queue_arg>).
136 #false laziness w/part_export.pm
138 my( $self, @args ) = @_;
140 local $SIG{HUP} = 'IGNORE';
141 local $SIG{INT} = 'IGNORE';
142 local $SIG{QUIT} = 'IGNORE';
143 local $SIG{TERM} = 'IGNORE';
144 local $SIG{TSTP} = 'IGNORE';
145 local $SIG{PIPE} = 'IGNORE';
147 my $oldAutoCommit = $FS::UID::AutoCommit;
148 local $FS::UID::AutoCommit = 0;
157 $self->custnum( $args{'custnum'} ) if $args{'custnum'};
159 $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum;
161 my $error = $self->SUPER::insert;
163 $dbh->rollback if $oldAutoCommit;
167 foreach my $arg ( @args ) {
168 my $freeze = ref($arg) ? 'Y' : '';
169 my $queue_arg = new FS::queue_arg ( {
170 'jobnum' => $self->jobnum,
172 'arg' => $freeze ? encode_base64(nfreeze($arg)) : $arg,# always freeze?
174 $error = $queue_arg->insert;
176 $dbh->rollback if $oldAutoCommit;
182 warn "jobnums global is active: $jobnums\n" if $DEBUG;
183 push @$jobnums, $self->jobnum;
186 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
194 Delete this record from the database. Any corresponding queue_arg records are
202 local $SIG{HUP} = 'IGNORE';
203 local $SIG{INT} = 'IGNORE';
204 local $SIG{QUIT} = 'IGNORE';
205 local $SIG{TERM} = 'IGNORE';
206 local $SIG{TSTP} = 'IGNORE';
207 local $SIG{PIPE} = 'IGNORE';
209 my $oldAutoCommit = $FS::UID::AutoCommit;
210 local $FS::UID::AutoCommit = 0;
213 my @del = qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } );
214 push @del, qsearch( 'queue_depend', { 'depend_jobnum' => $self->jobnum } );
217 if ( $self->status =~/^done/ ) {
218 my $dropstring = rooturl(). '/misc/queued_report\?report=';
219 if ($self->statustext =~ /.*$dropstring([.\w]+)\>/) {
220 $reportname = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/report.$1";
224 my $error = $self->SUPER::delete;
226 $dbh->rollback if $oldAutoCommit;
230 foreach my $del ( @del ) {
231 $error = $del->delete;
233 $dbh->rollback if $oldAutoCommit;
238 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
240 unlink $reportname if $reportname;
246 =item replace OLD_RECORD
248 Replaces the OLD_RECORD with this one in the database. If there is an error,
249 returns the error, otherwise returns false.
253 # the replace method can be inherited from FS::Record
257 Checks all fields to make sure this is a valid job. If there is
258 an error, returns the error, otherwise returns false. Called by the insert
266 $self->ut_numbern('jobnum')
267 || $self->ut_anything('job')
268 || $self->ut_numbern('_date')
269 || $self->ut_enum('status',['', qw( new locked failed done )])
270 || $self->ut_anything('statustext')
271 || $self->ut_numbern('svcnum')
272 || $self->ut_foreign_keyn('usernum', 'access_user', 'usernum')
274 return $error if $error;
276 $error = $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum');
277 $self->svcnum('') if $error;
279 $self->status('new') unless $self->status;
280 $self->_date(time) unless $self->_date;
287 Returns a list of the arguments associated with this job.
293 map { $_->frozen ? thaw(decode_base64($_->arg)) : $_->arg }
294 qsearch( 'queue_arg',
295 { 'jobnum' => $self->jobnum },
303 Returns the FS::cust_svc object associated with this job, if any.
309 qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
314 Returns the FS::queue_depend objects associated with this job, if any.
315 (Dependancies that must complete before this job can be run).
321 qsearch('queue_depend', { 'jobnum' => $self->jobnum } );
324 =item depend_insert OTHER_JOBNUM
326 Inserts a dependancy for this job - it will not be run until the other job
327 specified completes. If there is an error, returns the error, otherwise
330 When using job dependancies, you should wrap the insertion of all relevant jobs
331 in a database transaction.
336 my($self, $other_jobnum) = @_;
337 my $queue_depend = new FS::queue_depend ( {
338 'jobnum' => $self->jobnum,
339 'depend_jobnum' => $other_jobnum,
341 $queue_depend->insert;
346 Returns the FS::queue_depend objects that associate other jobs with this job,
347 if any. (The jobs that are waiting for this job to complete before they can
354 qsearch('queue_depend', { 'depend_jobnum' => $self->jobnum } );
357 =item depended_delete
359 Deletes the other queued jobs (FS::queue objects) that are waiting for this
360 job, if any. If there is an error, returns the error, otherwise returns false.
364 sub depended_delete {
368 map { qsearchs('queue', { 'jobnum' => $_->jobnum } ) } $self->queue_depended
370 $error = $job->depended_delete;
371 return $error if $error;
372 $error = $job->delete;
373 return $error if $error
377 =item update_statustext VALUE
379 Updates the statustext value of this job to supplied value, in the database.
380 If there is an error, returns the error, otherwise returns false.
384 use vars qw($_update_statustext_dbh);
385 sub update_statustext {
386 my( $self, $statustext ) = @_;
387 return '' if $statustext eq $self->get('statustext'); #avoid rooturl expansion
388 warn "updating statustext for $self to $statustext" if $DEBUG;
390 $_update_statustext_dbh ||= myconnect;
392 my $sth = $_update_statustext_dbh->prepare(
393 'UPDATE queue set statustext = ? WHERE jobnum = ?'
394 ) or return $_update_statustext_dbh->errstr;
396 $sth->execute($statustext, $self->jobnum) or return $sth->errstr;
397 $_update_statustext_dbh->commit or die $_update_statustext_dbh->errstr;
398 $self->set('statustext', $statustext); #avoid rooturl expansion
401 #my $new = new FS::queue { $self->hash };
402 #$new->statustext($statustext);
403 #my $error = $new->replace($self);
404 #return $error if $error;
405 #$self->statustext($statustext);
412 #Returns FS::access_user object (if any) associated with this user.
414 #Returns nothing if not found.
420 # my $usernum = $self->usernum || return ();
421 # return qsearchs('access_user',{ 'usernum' => $usernum }) || ();
430 =item joblisting HASHREF NOACTIONS
435 my($hashref, $noactions) = @_;
441 my @queue = qsearch( 'queue', $hashref );
442 return '' unless scalar(@queue);
444 my $p = FS::CGI::popurl(2);
446 my $html = qq!<FORM ACTION="$p/misc/queue.cgi" METHOD="POST">!.
447 FS::CGI::table(). <<END;
449 <TH COLSPAN=2>Job</TH>
454 $html .= '<TH>Account</TH>' unless $hashref->{svcnum};
457 my $dangerous = $conf->exists('queue_dangerous_controls');
461 foreach my $queue ( sort {
462 $a->getfield('jobnum') <=> $b->getfield('jobnum')
464 my $queue_hashref = $queue->hashref;
465 my $jobnum = $queue->jobnum;
468 if ( $dangerous || $queue->job !~ /^FS::part_export::/ || !$noactions ) {
469 $args = encode_entities( join(' ', $queue->args) );
474 my $date = time2str( "%a %b %e %T %Y", $queue->_date );
475 my $status = $queue->status;
476 $status .= ': '. $queue->statustext if $queue->statustext;
477 my @queue_depend = $queue->queue_depend;
478 $status .= ' (waiting for '.
479 join(', ', map { $_->depend_jobnum } @queue_depend ).
482 my $changable = $dangerous
483 || ( ! $noactions && $status =~ /^failed/ || $status =~ /^locked/ );
486 qq! ( <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A> |!.
487 qq! <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A> )!;
489 my $cust_svc = $queue->cust_svc;
494 <TD>$queue_hashref->{job}</TD>
500 unless ( $hashref->{svcnum} ) {
503 my $table = $cust_svc->part_svc->svcdb;
504 my $label = ( $cust_svc->label )[1];
505 $account = qq!<A HREF="../view/$table.cgi?!. $queue->svcnum.
510 $html .= "<TD>$account</TD>";
516 qq!<TD><INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1"></TD>!;
527 $html .= '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'.
528 '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>';
543 L<FS::Record>, schema.html from the base documentation.