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);
411 Returns FS::access_user object (if any) associated with this user.
413 Returns nothing if not found.
419 my $usernum = $self->usernum || return ();
420 return qsearchs('access_user',{ 'usernum' => $usernum }) || ();
429 =item joblisting HASHREF NOACTIONS
434 my($hashref, $noactions) = @_;
440 my @queue = qsearch( 'queue', $hashref );
441 return '' unless scalar(@queue);
443 my $p = FS::CGI::popurl(2);
445 my $html = qq!<FORM ACTION="$p/misc/queue.cgi" METHOD="POST">!.
446 FS::CGI::table(). <<END;
448 <TH COLSPAN=2>Job</TH>
453 $html .= '<TH>Account</TH>' unless $hashref->{svcnum};
456 my $dangerous = $conf->exists('queue_dangerous_controls');
460 foreach my $queue ( sort {
461 $a->getfield('jobnum') <=> $b->getfield('jobnum')
463 my $queue_hashref = $queue->hashref;
464 my $jobnum = $queue->jobnum;
467 if ( $dangerous || $queue->job !~ /^FS::part_export::/ || !$noactions ) {
468 $args = encode_entities( join(' ', $queue->args) );
473 my $date = time2str( "%a %b %e %T %Y", $queue->_date );
474 my $status = $queue->status;
475 $status .= ': '. $queue->statustext if $queue->statustext;
476 my @queue_depend = $queue->queue_depend;
477 $status .= ' (waiting for '.
478 join(', ', map { $_->depend_jobnum } @queue_depend ).
481 my $changable = $dangerous
482 || ( ! $noactions && $status =~ /^failed/ || $status =~ /^locked/ );
485 qq! ( <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A> |!.
486 qq! <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A> )!;
488 my $cust_svc = $queue->cust_svc;
493 <TD>$queue_hashref->{job}</TD>
499 unless ( $hashref->{svcnum} ) {
502 my $table = $cust_svc->part_svc->svcdb;
503 my $label = ( $cust_svc->label )[1];
504 $account = qq!<A HREF="../view/$table.cgi?!. $queue->svcnum.
509 $html .= "<TD>$account</TD>";
515 qq!<TD><INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1"></TD>!;
526 $html .= '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'.
527 '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>';
542 L<FS::Record>, schema.html from the base documentation.