MARC import: part 4 of large file support
[koha.git] / C4 / BackgroundJob.pm
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 under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 2 of the License, or (at your option) any later
11 # version.
12 #
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along with
18 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
19 # Suite 330, Boston, MA  02111-1307 USA
20
21 use strict;
22 use C4::Context;
23 use C4::Auth qw/get_session/;
24 use Digest::MD5;
25
26 use vars qw($VERSION);
27
28 # set the version for version checking
29 $VERSION = 3.00;
30
31 =head1 NAME
32
33 C4::BackgroundJob - manage long-running jobs
34 initiated from the web staff interface
35
36 =head1 SYNOPSIS
37
38 =over 4
39
40 # start tracking a job
41 my $job = C4::BackgroundJob->new($sessionID, $job_name, $job_invoker, $num_work_units);
42 my $jobID = $job->id();
43 $job->progress($work_units_processed);
44 $job->finish($job_result_hashref);
45
46 # get status and results of a job
47 my $job = C4::BackgroundJob->fetch($sessionID, $jobID);
48 my $max_work_units = $job->size();
49 my $work_units_processed = $job->progress();
50 my $job_status = $job->status();
51 my $job_name = $job->name();
52 my $job_invoker = $job->invoker();
53 my $results_hashref = $job->results();
54
55 =back
56
57 This module manages tracking the progress and results
58 of (potentially) long-running jobs initiated from 
59 the staff user interface.  Such jobs can include
60 batch MARC and patron record imports.
61
62 =cut
63
64 =head1 METHODS
65
66 =cut
67
68 =head2 new
69
70 =over 4
71
72 my $job = C4::BackgroundJob->new($sessionID, $job_name, $job_invoker, $num_work_units);
73
74 =back
75
76 Create a new job object and set its status to 'running'.  C<$num_work_units>
77 should be a number representing the size of the job; the units of the
78 job size are up to the caller and could be number of records, 
79 number of bytes, etc.
80
81 =cut
82
83 sub new {
84     my $class = shift;
85     my ($sessionID, $job_name, $job_invoker, $num_work_units) = @_;
86
87     my $self = {};
88     $self->{'sessionID'} = $sessionID;
89     $self->{'name'} = $job_name;
90     $self->{'invoker'} = $job_invoker;
91     $self->{'size'} = $num_work_units;
92     $self->{'progress'} = 0;
93     $self->{'status'} = "running";
94     $self->{'jobID'} = Digest::MD5::md5_hex(Digest::MD5::md5_hex(time().{}.rand().{}.$$));
95
96     bless $self, $class;
97     $self->_serialize();
98
99     return $self;
100 }
101
102 # store object in CGI session
103 sub _serialize {
104     my $self = shift;
105
106     my $prefix = "job_" . $self->{'jobID'};
107     my $session = get_session($self->{'sessionID'});
108     $session->param("$prefix.name", $self->{'name'});
109     $session->param("$prefix.invoker", $self->{'invoker'});
110     $session->param("$prefix.size", $self->{'size'});
111     $session->param("$prefix.progress", $self->{'size'});
112     $session->param("$prefix.status", $self->{'size'});
113     if (exists $self->{'results'}) {
114         my @keys = ();
115         foreach my $key (keys %{ $self->{'results'} }) {
116             $session->param("$prefix.results.$key", $self->{'results'}->{$key});
117             push @keys, $key;
118         }
119         $session->param("$prefix.results_keys", join("\t", @keys));
120     }
121     $session->flush();
122 }
123
124 =head2 id
125
126 =over 4
127
128 my $jobID = $job->id();
129
130 =back
131
132 Read-only accessor for job ID.
133
134 =cut
135
136 sub id {
137     my $self = shift;
138     return $self->{'id'};
139 }
140
141 =head2 name
142
143 =over 4
144
145 my $name = $job->name();
146 $job->name($name);
147
148 =back
149
150 Read/write accessor for job name.
151
152 =cut
153
154 sub name {
155     my $self = shift;
156     if (@_) {
157         $self->{'name'} = shift;
158         $self->_serialize();
159     } else {
160         return $self->{'name'};
161     }
162 }
163
164 =head2 invoker
165
166 =over 4
167
168 my $invoker = $job->invoker();
169 $job->invoker($invoker);
170
171 =back
172
173 Read/write accessor for job invoker.
174
175 =cut
176
177 sub invoker {
178     my $self = shift;
179     if (@_) {
180         $self->{'invoker'} = shift;
181         $self->_serialize();
182     } else {
183         return $self->{'invoker'};
184     }
185 }
186
187 =head2 progress
188
189 =over 4
190
191 my $progress = $job->progress();
192 $job->progress($progress);
193
194 =back
195
196 Read/write accessor for job progress.
197
198 =cut
199
200 sub progress {
201     my $self = shift;
202     if (@_) {
203         $self->{'progress'} = shift;
204         $self->_serialize();
205     } else {
206         return $self->{'progress'};
207     }
208 }
209
210 =head2 status
211
212 =over 4
213
214 my $status = $job->status();
215
216 =back
217
218 Read-only accessor for job status.
219
220 =cut
221
222 sub status {
223     my $self = shift;
224     return $self->{'status'};
225 }
226
227 =head2 size
228
229 =over 4
230
231 my $size = $job->size();
232 $job->size($size);
233
234 =back
235
236 Read/write accessor for job size.
237
238 =cut
239
240 sub size {
241     my $self = shift;
242     if (@_) {
243         $self->{'size'} = shift;
244         $self->_serialize();
245     } else {
246         return $self->{'size'};
247     }
248 }
249
250 =head2 finish
251
252 =over 4
253
254 $job->finish($results_hashref);
255
256 =back
257
258 Mark the job as finished, setting its status to 'completed'.
259 C<$results_hashref> should be a reference to a hash containing
260 the results of the job.
261
262 =cut
263
264 sub finish {
265     my $self = shift;
266     my $results_hashref = shift;
267     my $self->{'status'} = 'completed';
268     my $self->{'results'} = $results_hashref;
269     $self->_serialize();
270 }
271
272 =head2 results
273
274 =over 4
275
276 my $results_hashref = $job->results();
277
278 =back
279
280 Retrieve the results of the current job.  Returns undef 
281 if the job status is not 'completed'.
282
283 =cut
284
285 sub results {
286     my $self = shift;
287     return undef unless $self->{'status'} eq 'completed';
288     return $self->{'results'};
289 }
290
291 =head2 fetch
292
293 =over 4
294
295 my $job = C4::BackgroundJob->fetch($sessionID, $jobID);
296
297 =back
298
299 Retrieve a job that has been serialized to the database. 
300 Returns C<undef> if the job does not exist in the current 
301 session.
302
303 =cut
304
305 sub fetch {
306     my $class = shift;
307     my $sessionID = shift;
308     my $jobID = shift;
309
310     my $session = get_session($sessionID);
311     my $prefix = "job_$jobID";
312     unless (defined $session->param("$prefix.name")) {
313         return undef;
314     }
315     my $self = {};
316     
317     $self->{'name'} = $session->param("$prefix.name");
318     $self->{'status'} = $session->param("$prefix.status");
319     $self->{'invoker'} = $session->param("$prefix.invoker");
320     $self->{'size'} = $session->param("$prefix.size");
321     $self->{'progress'} = $session->param("$prefix.progress");
322     if (defined(my $keys = $session->param("$prefix.results_keys"))) {
323         my @keys = split /\t/, $keys;
324         $self->{'results'} = {};
325         foreach my $key (@keys) {
326             $self->{'results'}->{$key} = $session->param("$prefix.results.$key");
327         }
328     }
329
330     bless $self, $class;
331     return $self;
332 }
333
334 =head1 AUTHOR
335
336 Koha Development Team <info@koha.org>
337
338 Galen Charlton <galen.charlton@liblime.com>
339
340 =cut