Completed
Pull Request — master (#116)
by Janis
12:40
created

JobService::handleProcessed()   B

Complexity

Conditions 4
Paths 13

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4.0047

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
ccs 14
cts 15
cp 0.9333
rs 8.9197
cc 4
eloc 17
nc 13
nop 0
crap 4.0047
1
<?php
2
3
/**
4
 * Nextcloud - OCR
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later.
7
 * See the COPYING file.
8
 * 
9
 * @author Janis Koehr <[email protected]>
10
 * @copyright Janis Koehr 2017
11
 */
12
namespace OCA\Ocr\Service;
13
14
use OCA\Ocr\Db\OcrJobMapper;
15
use OC\Files\View;
16
use OCP\ILogger;
17
use OCP\IL10N;
18
use OCP\ITempManager;
19
use OCA\Ocr\Db\OcrJob;
20
use OCA\Ocr\Constants\OcrConstants;
21
use OCA\Ocr\Util\PHPUtil;
22
use Exception;
23
use OCP\AppFramework\Db\DoesNotExistException;
24
use OCA\Ocr\Util\FileUtil;
25
26
27
/**
28
 * Class JobService
29
 * 
30
 * @package OCA\Ocr\Service
31
 */
32
class JobService {
33
34
    /**
35
     *
36
     * @var ILogger
37
     */
38
    private $logger;
39
40
    /**
41
     *
42
     * @var RedisService
43
     */
44
    private $redisService;
45
46
    /**
47
     *
48
     * @var OcrJobMapper
49
     */
50
    private $jobMapper;
51
52
    /**
53
     *
54
     * @var View
55
     */
56
    private $view;
57
58
    /**
59
     *
60
     * @var String
61
     */
62
    private $userId;
63
64
    /**
65
     *
66
     * @var IL10N
67
     */
68
    private $l10n;
69
70
    /**
71
     *
72
     * @var FileService
73
     */
74
    private $fileService;
75
76
    /**
77
     *
78
     * @var ITempManager
79
     */
80
    private $tempM;
81
82
    /**
83
     *
84
     * @var AppConfigService
85
     */
86
    private $appConfigService;
87
88
    /**
89
     *
90
     * @var PHPUtil
91
     */
92
    private $phpUtil;
93
94
    /**
95
     *
96
     * @var FileUtil
97
     */
98
    private $fileUtil;
99
100
    /**
101
     * JobService constructor.
102
     * 
103
     * @param IL10N $l10n            
104
     * @param ILogger $logger            
105
     * @param string $userId            
106
     * @param View $view            
107
     * @param RedisService $queueService            
108
     * @param OcrJobMapper $mapper            
109
     * @param FileService $fileService            
110
     * @param AppConfigService $appConfigService            
111
     * @param PHPUtil $phpUtil            
112
     * @param FileUtil $fileUtil            
113
     */
114 22
    public function __construct(IL10N $l10n, ILogger $logger, $userId, View $view, ITempManager $tempManager, 
115
            RedisService $queueService, OcrJobMapper $mapper, FileService $fileService, 
116
            AppConfigService $appConfigService, PHPUtil $phpUtil, FileUtil $fileUtil) {
117 22
        $this->logger = $logger;
118 22
        $this->redisService = $queueService;
119 22
        $this->jobMapper = $mapper;
120 22
        $this->view = $view;
121 22
        $this->userId = $userId;
122 22
        $this->l10n = $l10n;
123 22
        $this->fileService = $fileService;
124 22
        $this->tempM = $tempManager;
125 22
        $this->appConfigService = $appConfigService;
126 22
        $this->phpUtil = $phpUtil;
127 22
        $this->fileUtil = $fileUtil;
128 22
    }
129
130
    /**
131
     * Processes and prepares the files for OCR.
132
     * Sends the stuff to the client in order to OCR async.
133
     * 
134
     * @param string[] $languages            
135
     * @param array $files            
136
     * @param boolean $replace            
137
     * @return string
138 9
     */
139
    public function process($languages, $files, $replace) {
140 9
        try {
141
            $this->logger->debug('Will now process files: {files} with languages: {languages} and replace: {replace}', 
142 9
                    [
143 9
                            'files' => json_encode($files),
144
                            'languages' => json_encode($languages),
145
                            'replace' => json_encode($replace)
146 9
                    ]);
147 9
            // Check if files and language not empty
148
            $noLang = $this->noLanguage($languages);
149 6
            if (!empty($files) && ($this->checkForAcceptedLanguages($languages) || $noLang) && is_bool($replace)) {
150 5
                // language part:
151
                if ($noLang) {
152
                    $languages = [];
153 6
                }
154 6
                // file part:
155
                $fileInfo = $this->fileService->buildFileInfo($files);
156 6
                foreach ($fileInfo as $fInfo) {
157 6
                    // Check Shared
158 6
                    $shared = $this->fileService->checkSharedWithInitiator($fInfo);
159
                    $target = $this->fileService->buildTarget($fInfo, $shared);
160 6
                    $source = $this->fileService->buildSource($fInfo, $shared);
161
                    // set the running type
162 6
                    $fType = $this->fileService->getCorrectType($fInfo);
163
                    // create a temp file for ocr processing purposes
164 2
                    $tempFile = $this->getTempFile($fType);
165 2
                    // Create job object
166
                    $job = new OcrJob(OcrConstants::STATUS_PENDING, $source, $target, $tempFile, $fType, $this->userId, 
167
                            false, $fInfo->getName(), null, $replace);
168 2
                    // Init client and send task / job
169
                    // Feed the worker
170 2
                    $this->redisService->sendJob($job, $languages);
171
                }
172 3
                return 'PROCESSING';
173
            } else {
174 7
                throw new NotFoundException($this->l10n->t('Empty parameters passed.'));
175 7
            }
176
        } catch (Exception $e) {
177
            $this->handleException($e);
178
        }
179
    }
180
181
    /**
182
     * Delete an ocr job for a given id and userId.
183
     * 
184
     * @param
185
     *            $jobId
186
     * @param string $userId            
187 3
     * @return OcrJob
188
     */
189 3
    public function deleteJob($jobId, $userId) {
190 2
        try {
191 1
            $job = $this->jobMapper->find($jobId);
192
            if ($job->getUserId() !== $userId) {
193 1
                throw new NotFoundException($this->l10n->t('Cannot delete because of wrong owner.'));
194
            } else {
195 1
                $job = $this->jobMapper->delete($job);
196 1
            }
197 1
            $job->setTarget(null);
198 1
            $job->setSource(null);
199 1
            $job->setStatus(null);
200 1
            $job->setTempFile(null);
201 1
            $job->setType(null);
202 1
            $job->setUserId(null);
203 1
            $job->setErrorDisplayed(null);
204 2
            $job->setErrorLog(null);
205 2
            $job->setReplace(null);
206 1
            return $job;
207 1
        } catch (Exception $e) {
208
            if ($e instanceof DoesNotExistException) {
0 ignored issues
show
Bug introduced by
The class OCP\AppFramework\Db\DoesNotExistException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
209 1
                $ex = new NotFoundException($this->l10n->t('Cannot delete because of wrong ID.'));
210
                $this->handleException($ex);
211
            } else {
212
                $this->handleException($e);
213
            }
214
        }
215
    }
216
217
    /**
218
     * Gets all job objects for a specific user.
219
     * 
220 1
     * @param string $userId            
221
     * @return OcrJob[]
222 1
     */
223 1
    public function getAllJobsForUser($userId) {
224 1
        try {
225 1
            $jobs = $this->jobMapper->findAll($userId);
226 1
            $jobsNew = array();
227 1
            foreach ($jobs as $job) {
228 1
                $job->setTarget(null);
229 1
                $job->setSource(null);
230 1
                $job->setTempFile(null);
231 1
                $job->setType(null);
232
                $job->setUserId(null);
233 1
                $job->setErrorDisplayed(null);
234
                array_push($jobsNew, $job);
235
            }
236
            return $jobsNew;
237
        } catch (Exception $e) {
238
            $this->handleException($e);
239
        }
240
    }
241
242
    /**
243
     * The function checks if there are finished jobs to process finally.
244 2
     * 
245
     * @throws NotFoundException
246 2
     */
247 2
    public function checkForFinishedJobs() {
248 1
        try {
249 1
            $finishedJobs = $this->redisService->readingFinishedJobs();
250
            foreach ($finishedJobs as $finishedJob) {
251 1
                $fJob = $this->transformJob($finishedJob);
252
                $this->logger->debug('The following job finished: {job}', 
253 1
                        [
254
                                'job' => $fJob
255 1
                        ]);
256 1
                $this->jobFinished($fJob->id, $fJob->error, $fJob->log);
257
            }
258 1
        } catch (Exception $e) {
259
            throw new NotFoundException($this->l10n->t('Reading the finished jobs from Redis went wrong.'));
260
        }
261
    }
262
263
    /**
264
     * Finishes all Processed files by copying them to the right path and deleteing the temp files.
265
     * Returns the number of processed files.
266 2
     * 
267
     * @return array
268 2
     */
269 2
    public function handleProcessed() {
270 2
        try {
271 2
            $this->logger->debug('Check if files were processed by ocr and if so, put them to the right dirs.');
272
            $processed = $this->jobMapper->findAllProcessed($this->userId);
273 1
            foreach ($processed as $job) {
274 1
                if ($this->fileUtil->fileExists($job->getTempFile())) {
275 1
                    // Save the tmp file with newname
276 1
                    $this->pullResult($job);
277
                    $this->jobMapper->delete($job);
278 1
                    $this->fileUtil->execRemove($job->getTempFile());
279 1
                } else {
280 1
                    $job->setStatus(OcrConstants::STATUS_FAILED);
281 2
                    $job->setErrorLog('Temp file does not exist.');
282
                    $this->jobMapper->update($job);
283
                    throw new NotFoundException($this->l10n->t('Temp file does not exist.'));
284 1
                }
285 1
            }
286 1
            return $processed;
287
        } catch (Exception $e) {
288
            $this->handleException($e);
289
        }
290
    }
291
292
    /**
293
     * Handles all failed orders of ocr processing queue and returns the job objects.
294
     * 
295 1
     * @return array
296
     */
297 1
    public function handleFailed() {
298 1
        try {
299
            $failed = $this->jobMapper->findAllFailed($this->userId);
300 1
            foreach ($failed as $job) {
301
                // clean the tempfile
302 1
                $this->fileUtil->execRemove($job->getTempFile());
303 1
                // set error displayed
304
                $job->setErrorDisplayed(true);
305 1
                $this->jobMapper->update($job);
306
            }
307 1
            $this->logger->debug('Following jobs failed: {failed}', 
308
                    [
309 1
                            'failed' => json_encode($failed)
310
                    ]);
311
            return $failed;
312
        } catch (Exception $e) {
313
            $this->handleException($e);
314
        }
315
    }
316
317
    /**
318
     * Gets the OCR result and puts it to Nextcloud.
319
     * 
320
     * @param OcrJob $job            
321
     */
322
    private function pullResult($job) {
323 1
        if ($job->getReplace() && $job->getType() !== OcrConstants::OCRmyPDF) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
324
            // TODO: historisiere
325 1
        }
326 1
        $this->view->file_put_contents($job->getTarget(), $this->fileUtil->getFileContents($job->getTempFile()));
327 1
    }
328 1
329
    /**
330 1
     * The function the worker will call in order to set the jobs status.
331 1
     * The worker should call it automatically after each processing step.
332 1
     * 
333 1
     * @param integer $jobId            
334
     * @param boolean $error            
335
     * @param string $log            
336
     */
337
    private function jobFinished($jobId, $error, $log) {
338 1
        try {
339
            $job = $this->jobMapper->find($jobId);
340
            if (!$error) {
341
                $job->setStatus(OcrConstants::STATUS_PROCESSED);
342
                $this->jobMapper->update($job);
343
            } else {
344
                $job->setStatus(OcrConstants::STATUS_FAILED);
345
                $job->setErrorLog($log);
346
                $this->jobMapper->update($job);
347 6
                $this->logger->error($log);
348 6
            }
349 5
        } catch (Exception $e) {
350 5
            $this->handleException($e);
351 4
        }
352 4
    }
353 3
354 2
    /**
355
     * Gives a temp file name back depending on the type of the OCR.
356 3
     * Later in the worker this file is used as an output.
357
     * 
358
     * @param integer $type            
359
     * @return string
360
     */
361
    private function getTempFile($type) {
362
        if ($type === OcrConstants::TESSERACT) {
363
            $fileName = $this->phpUtil->tempnamWrapper($this->tempM->getTempBaseDir(), OcrConstants::TEMPFILE_PREFIX);
364
            $this->phpUtil->unlinkWrapper($fileName);
365
            $fileNameWithPostfix = $fileName . '.txt';
366
            $this->phpUtil->touchWrapper($fileNameWithPostfix);
367 1
            $this->phpUtil->chmodWrapper($fileNameWithPostfix, 0600);
368 1
            return $fileNameWithPostfix;
369 1
        } else {
370 1
            return $this->phpUtil->tempnamWrapper($this->tempM->getTempBaseDir(), OcrConstants::TEMPFILE_PREFIX);
371
        }
372
    }
373
374
    /**
375
     * Takes care of transforming an incoming finished job into a php readable object.
376
     * 
377
     * @param string $job            
378
     * @throws NotFoundException
379
     * @return mixed
380
     */
381
    private function transformJob($job) {
382
        $decoded = json_decode($job);
383 8
        if ($decoded !== null && isset($decoded->id)) {
384 8
            return $decoded;
385 8
        } else {
386 1
            $this->logger->debug('The finished job retrieved by Redis was corrupt.');
387
            throw new NotFoundException('The finished job retrieved by Redis was corrupt.');
388 7
        }
389
    }
390
391
    /**
392
     * Checks if the given languages are supported or not.
393
     * 
394
     * @param string[] $languages            
395
     * @return boolean
396
     */
397
    private function checkForAcceptedLanguages($languages) {
398 9
        $installedLanguages = explode(';', $this->appConfigService->getAppValue('languages'));
399 9
        if (count(array_diff($languages, $installedLanguages)) === 0) {
400 6
            return true;
401
        } else {
402 3
            return false;
403
        }
404
    }
405
406
    /**
407
     * Checks if the process should be initiated without any language specified.
408
     * 
409
     * @param string[] $languages            
410
     * @return boolean
411
     */
412
    private function noLanguage($languages) {
413 10
        if (in_array('any', $languages)) {
414 10
            return true;
415
        } else {
416
            return false;
417 10
        }
418 10
    }
419
420
    /**
421
     * Handle the possible thrown Exceptions from all methods of this class.
422
     * 
423
     * @param Exception $e            
424
     * @throws Exception
425
     * @throws NotFoundException
426
     */
427
    private function handleException($e) {
428
        $this->logger->logException($e, 
429
                [
430
                        'message' => 'Exception during job service function processing'
431
                ]);
432
        throw $e;
433
    }
434
}