View | Details | Raw Unified | Return to bug 33963
Collapse All | Expand All

(-)a/C4/BackgroundJob.pm (-325 lines)
Lines 1-325 Link Here
1
package C4::BackgroundJob;
2
3
# Copyright (C) 2007 LibLime
4
# Galen Charlton <galen.charlton@liblime.com>
5
#
6
# This file is part of Koha.
7
#
8
# Koha is free software; you can redistribute it and/or modify it
9
# under the terms of the GNU General Public License as published by
10
# the Free Software Foundation; either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# Koha is distributed in the hope that it will be useful, but
14
# WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21
use Modern::Perl;
22
use C4::Context;
23
use C4::Auth qw( get_session );
24
use Digest::MD5;
25
26
27
28
=head1 NAME
29
30
C4::BackgroundJob - manage long-running jobs
31
initiated from the web staff interface
32
33
=head1 SYNOPSIS
34
35
 # start tracking a job
36
 my $job = C4::BackgroundJob->new($sessionID, $job_name, $job_invoker, $num_work_units);
37
 my $jobID = $job->id();
38
 $job->progress($work_units_processed);
39
 $job->finish($job_result_hashref);
40
41
 # get status and results of a job
42
 my $job = C4::BackgroundJob->fetch($sessionID, $jobID);
43
 my $max_work_units = $job->size();
44
 my $work_units_processed = $job->progress();
45
 my $job_status = $job->status();
46
 my $job_name = $job->name();
47
 my $job_invoker = $job->invoker();
48
 my $results_hashref = $job->results();
49
50
This module manages tracking the progress and results
51
of (potentially) long-running jobs initiated from 
52
the staff user interface.  Such jobs can include
53
batch MARC and patron record imports.
54
55
=head1 METHODS
56
57
=head2 new
58
59
 my $job = C4::BackgroundJob->new($sessionID, $job_name, $job_invoker, $num_work_units);
60
61
Create a new job object and set its status to 'running'.  C<$num_work_units>
62
should be a number representing the size of the job; the units of the
63
job size are up to the caller and could be number of records, 
64
number of bytes, etc.
65
66
=cut
67
68
sub new {
69
    my $class = shift;
70
    my ($sessionID, $job_name, $job_invoker, $num_work_units) = @_;
71
72
    my $self = {};
73
    $self->{'sessionID'} = $sessionID;
74
    $self->{'name'} = $job_name;
75
    $self->{'invoker'} = $job_invoker;
76
    $self->{'size'} = $num_work_units;
77
    $self->{'progress'} = 0;
78
    $self->{'status'} = "running";
79
    $self->{'jobID'} = Digest::MD5::md5_hex(Digest::MD5::md5_hex(time().{}.rand().{}.$$));
80
    $self->{'extra_values'} = {};
81
82
    bless $self, $class;
83
    $self->_serialize();
84
85
    return $self;
86
}
87
88
# store object in CGI session
89
sub _serialize {
90
    my $self = shift;
91
92
    my $prefix = "job_" . $self->{'jobID'};
93
    my $session = get_session($self->{'sessionID'});
94
    $session->param($prefix, $self);
95
    $session->flush();
96
}
97
98
=head2 id
99
100
 my $jobID = $job->id();
101
102
Read-only accessor for job ID.
103
104
=cut
105
106
sub id {
107
    my $self = shift;
108
    return $self->{'jobID'};
109
}
110
111
=head2 name
112
113
 my $name = $job->name();
114
 $job->name($name);
115
116
Read/write accessor for job name.
117
118
=cut
119
120
sub name {
121
    my $self = shift;
122
    if (@_) {
123
        $self->{'name'} = shift;
124
        $self->_serialize();
125
    } else {
126
        return $self->{'name'};
127
    }
128
}
129
130
=head2 invoker
131
132
 my $invoker = $job->invoker();
133
i $job->invoker($invoker);
134
135
Read/write accessor for job invoker.
136
137
=cut
138
139
sub invoker {
140
    my $self = shift;
141
    if (@_) {
142
        $self->{'invoker'} = shift;
143
        $self->_serialize();
144
    } else {
145
        return $self->{'invoker'};
146
    }
147
}
148
149
=head2 progress
150
151
 my $progress = $job->progress();
152
 $job->progress($progress);
153
154
Read/write accessor for job progress.
155
156
=cut
157
158
sub progress {
159
    my $self = shift;
160
    if (@_) {
161
        $self->{'progress'} = shift;
162
        $self->_serialize();
163
    } else {
164
        return $self->{'progress'};
165
    }
166
}
167
168
=head2 status
169
170
 my $status = $job->status();
171
172
Read-only accessor for job status.
173
174
=cut
175
176
sub status {
177
    my $self = shift;
178
    return $self->{'status'};
179
}
180
181
=head2 size
182
183
 my $size = $job->size();
184
 $job->size($size);
185
186
Read/write accessor for job size.
187
188
=cut
189
190
sub size {
191
    my $self = shift;
192
    if (@_) {
193
        $self->{'size'} = shift;
194
        $self->_serialize();
195
    } else {
196
        return $self->{'size'};
197
    }
198
}
199
200
=head2 finish
201
202
 $job->finish($results_hashref);
203
204
Mark the job as finished, setting its status to 'completed'.
205
C<$results_hashref> should be a reference to a hash containing
206
the results of the job.
207
208
=cut
209
210
sub finish {
211
    my $self = shift;
212
    my $results_hashref = shift;
213
    $self->{'status'} = 'completed';
214
    $self->{'results'} = $results_hashref;
215
    $self->_serialize();
216
}
217
218
=head2 results
219
220
 my $results_hashref = $job->results();
221
222
Retrieve the results of the current job.  Returns undef 
223
if the job status is not 'completed'.
224
225
=cut
226
227
sub results {
228
    my $self = shift;
229
    return unless $self->{'status'} eq 'completed';
230
    return $self->{'results'};
231
}
232
233
=head2 fetch
234
235
 my $job = C4::BackgroundJob->fetch($sessionID, $jobID);
236
237
Retrieve a job that has been serialized to the database. 
238
Returns C<undef> if the job does not exist in the current 
239
session.
240
241
=cut
242
243
sub fetch {
244
    my $class = shift;
245
    my $sessionID = shift;
246
    my $jobID = shift;
247
248
    my $session = get_session($sessionID);
249
    my $prefix = "job_$jobID";
250
    unless (defined $session->param($prefix)) {
251
        return;
252
    }
253
    my $self = $session->param($prefix);
254
    bless $self, $class;
255
    return $self;
256
}
257
258
=head2 set
259
260
=over 4
261
262
=item $job->set($hashref);
263
264
=back
265
266
Set some variables into the hashref.
267
These variables can be retrieved using the get method.
268
269
=cut
270
271
sub set {
272
    my ($self, $hashref) = @_;
273
    while ( my ($k, $v) = each %$hashref ) {
274
        $self->{extra_values}->{$k} = $v;
275
    }
276
    $self->_serialize();
277
    return;
278
}
279
280
=head2 get
281
282
=over 4
283
284
=item $value = $job->get($key);
285
286
=back
287
288
Get a variable which has been previously stored with the set method.
289
290
=cut
291
292
sub get {
293
    my ($self, $key) = @_;
294
    return $self->{extra_values}->{$key};
295
}
296
297
298
=head2 clear
299
300
=over 4
301
302
=item $job->clear();
303
304
=back
305
306
Clear the job from the current session.
307
308
=cut
309
310
sub clear {
311
    my $self = shift;
312
    get_session($self->{sessionID})->clear('job_' . $self->{jobID});
313
}
314
315
316
1;
317
__END__
318
319
=head1 AUTHOR
320
321
Koha Development Team <http://koha-community.org/>
322
323
Galen Charlton <galen.charlton@liblime.com>
324
325
=cut
(-)a/debian/templates/apache-shared-intranet-plack.conf (-1 lines)
Lines 11-17 Link Here
11
11
12
        # FIXME: These scripts should be fixed so they
12
        # FIXME: These scripts should be fixed so they
13
        # don't break under plack/starman
13
        # don't break under plack/starman
14
        ProxyPass "/cgi-bin/koha/tools/background-job-progress.pl" "!"
15
        ProxyPass "/cgi-bin/koha/tools/export.pl" "!"
14
        ProxyPass "/cgi-bin/koha/tools/export.pl" "!"
16
        ProxyPass "/cgi-bin/koha/tools/upload-cover-image.pl" "!"
15
        ProxyPass "/cgi-bin/koha/tools/upload-cover-image.pl" "!"
17
        ProxyPass "/cgi-bin/koha/svc/cataloguing/metasearch" "!"
16
        ProxyPass "/cgi-bin/koha/svc/cataloguing/metasearch" "!"
(-)a/koha-tmpl/intranet-tmpl/prog/js/background-job-progressbar.js (-84 lines)
Lines 1-84 Link Here
1
var backgroundJobProgressTimer = 0;
2
var jobID = '';
3
var savedForm;
4
var inBackgroundJobProgressTimer = false;
5
function updateJobProgress() {
6
    if (inBackgroundJobProgressTimer) {
7
        return;
8
    }
9
    inBackgroundJobProgressTimer = true;
10
    $.getJSON("/cgi-bin/koha/tools/background-job-progress.pl?jobID=" + jobID, function(json) {
11
        var percentage = json.job_status == 'completed' ? 100 :
12
                            json.job_size > 0              ? Math.floor(100 * json.progress / json.job_size) :
13
                            100;
14
        var bgproperty = (parseInt(percentage*2)-300)+"px 0px";
15
        $("#jobprogress").css("background-position",bgproperty);
16
        $("#jobprogresspercent").text(percentage);
17
18
        if (percentage == 100) {
19
            clearInterval(backgroundJobProgressTimer); // just in case form submission fails
20
            completeJob();
21
        }
22
        inBackgroundJobProgressTimer = false;
23
    });
24
}
25
26
function completeJob() {
27
    savedForm.completedJobID.value = jobID;
28
    savedForm.submit();
29
}
30
31
// submit a background job with data
32
// supplied from form f and activate
33
// progress indicator
34
function submitBackgroundJob(f) {
35
    // check for background field
36
    if (f.runinbackground) {
37
        // set value of this hidden field for
38
        // use by CGI script
39
        savedForm = f;
40
        f.mainformsubmit.disabled = true;
41
        f.runinbackground.value = 'true';
42
43
        // gather up form submission
44
        var inputs = [];
45
        $(':input:enabled', f).each(function() {
46
            if (this.type == 'radio' || this.type == 'checkbox') {
47
                if (this.checked) {
48
                    inputs.push(this.name + '=' + encodeURIComponent(this.value));
49
                }
50
            } else if (this.type == 'button') {
51
                ; // do nothing
52
            } else {
53
                inputs.push(this.name + '=' + encodeURIComponent(this.value));
54
            }
55
56
        });
57
58
        // and submit the request
59
        $("#jobpanel").show();
60
        $("#jobstatus").show();
61
        $.ajax({
62
            data: inputs.join('&'),
63
            url: f.action,
64
            dataType: 'json',
65
            type: 'post',
66
            success: function(json) {
67
                jobID = json.jobID;
68
                inBackgroundJobProgressTimer = false;
69
                backgroundJobProgressTimer = setInterval("updateJobProgress()", 500);
70
            },
71
            error: function(xml, textStatus) {
72
                humanMsg.displayMsg( '<p>' + __('Import of record(s) failed: ') + textStatus + '</p></br>'+xml.responseText, { className: 'humanError' } );
73
            }
74
75
        });
76
77
    } else {
78
        // background job support not enabled,
79
        // so just do a normal form submission
80
        f.submit();
81
    }
82
83
    return false;
84
}
(-)a/t/db_dependent/BackgroundJob.t (-57 lines)
Lines 1-57 Link Here
1
#!/usr/bin/perl
2
3
use Modern::Perl;
4
use C4::Auth qw( get_session );
5
use CGI qw ( -utf8 );
6
use Test::More tests => 18;
7
8
use Koha::Database;
9
10
BEGIN {
11
    use_ok('C4::BackgroundJob', qw( get id fetch name invoker progress status size set finish results clear ));
12
}
13
my $query = CGI->new;
14
15
my $schema = Koha::Database->new->schema;
16
$schema->storage->txn_begin;
17
18
my $session = C4::Auth::get_session;
19
$session->flush;
20
my $sessionID = $session->id;
21
my $job;
22
ok( $job = C4::BackgroundJob->new($sessionID), "making job" );
23
ok( $job->id, "fetching id number" );
24
25
$job->name("George");
26
is( $job->name, "George", "testing name" );
27
28
$job->invoker("enjoys");
29
is( $job->invoker, "enjoys", "testing invoker" );
30
31
$job->progress("testing");
32
is( $job->progress, "testing", "testing progress" );
33
34
ok( $job->status, "testing status" );
35
36
$job->size("56");
37
is( $job->size, "56", "testing size" );
38
39
ok( C4::BackgroundJob->fetch( $sessionID, $job->id ), "testing fetch" );
40
$job->set( { key1 => 'value1', key2 => 'value2' } );
41
is( $job->get('key1'), 'value1', 'fetched extra value for key key1' );
42
is( $job->get('key2'), 'value2', 'fetched extra value for key key2' );
43
44
$job->set( { size => 666 } );
45
is( $job->size, "56", '->set() does not scribble over private object data' );
46
47
$job->finish("finished");
48
is( $job->status, 'completed', "testing finished" );
49
50
ok( $job->results, 'Test if we have results' );
51
52
my $second_job = C4::BackgroundJob->new( $sessionID, "making new job" );
53
is( ref( C4::BackgroundJob->fetch( $sessionID, $job->id ) ),        "C4::BackgroundJob", 'job_$jobid should be a C4::BackgroundJob for uncleared job 1' );
54
is( ref( C4::BackgroundJob->fetch( $sessionID, $second_job->id ) ), "C4::BackgroundJob", 'job_$jobid should be a C4::BackgroundJob for uncleared job 2' );
55
$job->clear;
56
is( C4::BackgroundJob->fetch( $sessionID, $job->id ), undef, 'After clearing it, job 1 should not exist anymore in the session' );
57
is( ref( C4::BackgroundJob->fetch( $sessionID, $second_job->id ) ), "C4::BackgroundJob", 'After clear on job 1, job 2 should still be a C4::BackgroundJob' );
(-)a/tools/background-job-progress.pl (-57 lines)
Lines 1-56 Link Here
1
#!/usr/bin/perl
2
3
# Copyright (C) 2007 LibLime
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
# standard or CPAN modules used
23
use CGI qw ( -utf8 );
24
use CGI::Session;
25
use C4::Context;
26
use C4::Auth qw( check_cookie_auth );
27
use C4::BackgroundJob;
28
use CGI::Cookie; # need to check cookies before
29
                 # having CGI parse the POST request
30
31
my $input = CGI->new;
32
my %cookies = CGI::Cookie->fetch;
33
my ($auth_status, $session) = check_cookie_auth($cookies{'CGISESSID'}->value, { tools => '*' });
34
if ($auth_status ne "ok") {
35
    my $reply = CGI->new("");
36
    print $reply->header(-type => 'text/html');
37
    print '{"progress":"0"}';
38
    exit 0;
39
}
40
41
my $sessionID = $session->id;
42
my $jobID = $input->param('jobID');
43
my $job = C4::BackgroundJob->fetch($sessionID, $jobID);
44
my $reported_progress = 0;
45
my $job_size = 100;
46
my $job_status = 'running';
47
if (defined $job) {
48
    $reported_progress = $job->progress();
49
    $job_size = $job->size();
50
    $job_status = $job->status();
51
}
52
53
my $reply = CGI->new;
54
print $reply->header(-type => 'text/html');
55
# response will be sent back as JSON
56
print '{"progress":"' . $reported_progress . '","job_size":"' . $job_size . '","job_status":"' . $job_status . '"}';
57
- 

Return to bug 33963