Completed
Push — master ( 45d3c5...af8085 )
by
unknown
07:27
created

JobService::handleProcessed()   A

Complexity

Conditions 4
Paths 13

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4.0039

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
ccs 15
cts 16
cp 0.9375
rs 9.568
cc 4
nc 13
nop 0
crap 4.0039
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 24
    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 24
        $this->logger = $logger;
118 24
        $this->redisService = $queueService;
119 24
        $this->jobMapper = $mapper;
120 24
        $this->view = $view;
121 24
        $this->userId = $userId;
122 24
        $this->l10n = $l10n;
123 24
        $this->fileService = $fileService;
124 24
        $this->tempM = $tempManager;
125 24
        $this->appConfigService = $appConfigService;
126 24
        $this->phpUtil = $phpUtil;
127 24
        $this->fileUtil = $fileUtil;
128 24
    }
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
     */
139 11
    public function process($languages, $files, $replace) {
140
        try {
141 11
            $this->logger->debug('Will now process files: {files} with languages: {languages} and replace: {replace}', 
142
                    [
143 11
                            'files' => json_encode($files),
144 11
                            'languages' => json_encode($languages),
145 11
                            'replace' => json_encode($replace),
146
                            'app' => OcrConstants::APP_NAME
147
                    ]);
148
            // Check if files and language not empty
149 11
            $noLang = $this->noLanguage($languages);
150 11
            if (!empty($files) && ($this->checkForAcceptedLanguages($languages) || $noLang) && is_bool($replace)) {
151
                // language part:
152 7
                if ($noLang) {
153 5
                    $languages = [];
154
                }
155
                // file part:
156 7
                $fileInfo = $this->fileService->buildFileInfo($files);
157 7
                foreach ($fileInfo as $fInfo) {
158
                    // Check Shared
159 7
                    $shared = $this->fileService->checkSharedWithInitiator($fInfo);
160 7
                    if($shared && $replace) {
161 1
                        throw new NotFoundException($this->l10n->t('Cannot replace shared files.'));
162
                    }
163 6
                    $target = $this->fileService->buildTarget($fInfo, $shared, $replace);
164 6
                    $source = $this->fileService->buildSource($fInfo, $shared);
165
                    // set the running type
166 6
                    $fType = $this->fileService->getCorrectType($fInfo);
167
                    // create a temp file for ocr processing purposes
168 6
                    $tempFile = $this->getTempFile();
169
                    // Create job object
170 2
                    $job = new OcrJob(OcrConstants::STATUS_PENDING, $source, $target, $tempFile, $fType, $this->userId, 
171 2
                            false, $fInfo->getName(), null, $replace);
172
                    // Init client and send task / job
173
                    // Feed the worker
174 2
                    $this->redisService->sendJob($job, $languages);
175
                }
176 2
                return 'PROCESSING';
177
            } else {
178 4
                throw new NotFoundException($this->l10n->t('Empty parameters passed.'));
179
            }
180 9
        } catch (Exception $e) {
181 9
            $this->handleException($e);
182
        }
183
    }
184
185
    /**
186
     * Delete an ocr job for a given id and userId.
187
     * 
188
     * @param
189
     *            $jobId
190
     * @param string $userId            
191
     * @return OcrJob
192
     */
193 3
    public function deleteJob($jobId, $userId) {
194
        try {
195 3
            $job = $this->jobMapper->find($jobId);
196 2
            if ($job->getUserId() !== $userId) {
197 1
                throw new NotFoundException($this->l10n->t('Cannot delete because of wrong owner.'));
198
            } else {
199 1
                $job = $this->jobMapper->delete($job);
200
            }
201 1
            $job->setTarget(null);
202 1
            $job->setSource(null);
203 1
            $job->setStatus(null);
204 1
            $job->setTempFile(null);
205 1
            $job->setType(null);
206 1
            $job->setUserId(null);
207 1
            $job->setErrorDisplayed(null);
208 1
            $job->setErrorLog(null);
209 1
            $job->setReplace(null);
210 1
            return $job;
211 2
        } catch (Exception $e) {
212 2
            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...
213 1
                $ex = new NotFoundException($this->l10n->t('Cannot delete because of wrong ID.'));
214 1
                $this->handleException($ex);
215
            } else {
216 1
                $this->handleException($e);
217
            }
218
        }
219
    }
220
221
    /**
222
     * Gets all job objects for a specific user.
223
     * 
224
     * @param string $userId            
225
     * @return OcrJob[]
226
     */
227 1
    public function getAllJobsForUser($userId) {
228
        try {
229 1
            $jobs = $this->jobMapper->findAll($userId);
230 1
            $jobsNew = array();
231 1
            foreach ($jobs as $job) {
232 1
                $job->setTarget(null);
233 1
                $job->setSource(null);
234 1
                $job->setTempFile(null);
235 1
                $job->setType(null);
236 1
                $job->setUserId(null);
237 1
                $job->setErrorDisplayed(null);
238 1
                array_push($jobsNew, $job);
239
            }
240 1
            return $jobsNew;
241
        } catch (Exception $e) {
242
            $this->handleException($e);
243
        }
244
    }
245
246
    /**
247
     * The function checks if there are finished jobs to process finally.
248
     * 
249
     * @throws NotFoundException
250
     */
251 2
    public function checkForFinishedJobs() {
252
        try {
253 2
            $finishedJobs = $this->redisService->readingFinishedJobs();
254 2
            foreach ($finishedJobs as $finishedJob) {
255 1
                $fJob = $this->transformJob($finishedJob);
256 1
                $this->logger->debug('The following job finished: {job}', 
257
                        [
258 1
                                'job' => $fJob,
259
                                'app' => OcrConstants::APP_NAME
260
                        ]);
261 1
                $this->jobFinished($fJob->id, $fJob->error, $fJob->log);
262
            }
263 1
        } catch (Exception $e) {
264 1
            throw new NotFoundException($this->l10n->t('Reading the finished jobs from Redis went wrong.'));
265
        }
266 1
    }
267
268
    /**
269
     * Finishes all Processed files by copying them to the right path and deleteing the temp files.
270
     * Returns the number of processed files.
271
     * 
272
     * @return array
273
     */
274 2
    public function handleProcessed() {
275
        try {
276 2
            $this->logger->debug('Check if files were processed by ocr and if so, put them to the right dirs.', ['app' => OcrConstants::APP_NAME]);
277 2
            $processed = $this->jobMapper->findAllProcessed($this->userId);
278 2
            foreach ($processed as $job) {
279 2
                if ($this->fileUtil->fileExists($this->tempM->getTempBaseDir().'/'.$job->getTempFile().'.pdf')) {
280
                    // Save the tmp file with newname
281 1
                    $this->pullResult($job);
282 1
                    $this->jobMapper->delete($job);
283 1
                    $this->fileUtil->execRemove($this->tempM->getTempBaseDir().'/'.$job->getTempFile().'.pdf');
284
                } else {
285 1
                    $job->setStatus(OcrConstants::STATUS_FAILED);
286 1
                    $job->setErrorLog('Temp file does not exist.');
287 1
                    $this->jobMapper->update($job);
288 2
                    throw new NotFoundException($this->l10n->t('Temp file does not exist.'));
289
                }
290
            }
291 1
            return $processed;
292 1
        } catch (Exception $e) {
293 1
            $this->handleException($e);
294
        }
295
    }
296
297
    /**
298
     * Handles all failed orders of ocr processing queue and returns the job objects.
299
     * 
300
     * @return array
301
     */
302 1
    public function handleFailed() {
303
        try {
304 1
            $failed = $this->jobMapper->findAllFailed($this->userId);
305 1
            foreach ($failed as $job) {
306
                // clean the tempfile
307 1
                $this->fileUtil->execRemove($this->tempM->getTempBaseDir().'/'.$job->getTempFile().'.pdf');
308
                // set error displayed
309 1
                $job->setErrorDisplayed(true);
310 1
                $this->jobMapper->update($job);
311
            }
312 1
            $this->logger->debug('Following jobs failed: {failed}', 
313
                    [
314 1
                            'failed' => json_encode($failed),
315
                            'app' => OcrConstants::APP_NAME
316
                    ]);
317 1
            return $failed;
318
        } catch (Exception $e) {
319
            $this->handleException($e);
320
        }
321
    }
322
323
    /**
324
     * Gets the OCR result and puts it to Nextcloud.
325
     * 
326
     * @param OcrJob $job            
327
     */
328 1
    private function pullResult($job) {
329 1
        if ($job->getReplace()) {
330 1
            $this->view->unlink(str_replace($this->userId . '/files', '', $job->getSource()));
331
        }
332 1
        $result = $this->view->file_put_contents($job->getTarget(), $this->fileUtil->getFileContents($this->tempM->getTempBaseDir().'/'.$job->getTempFile().'.pdf'));
333 1
        if (!$result) {
334
            throw new NotFoundException($this->l10n->t('OCR could not put processed file to the right target folder. If you selected the replace option, you can restore the file by using the trash bin.'));
335
        }
336 1
    }
337
338
    /**
339
     * The function the worker will call in order to set the jobs status.
340
     * The worker should call it automatically after each processing step.
341
     * 
342
     * @param integer $jobId            
343
     * @param boolean $error            
344
     * @param string $log            
345
     */
346 1
    private function jobFinished($jobId, $error, $log) {
347
        try {
348 1
            $job = $this->jobMapper->find($jobId);
349 1
            if (!$error) {
350 1
                $job->setStatus(OcrConstants::STATUS_PROCESSED);
351 1
                $this->jobMapper->update($job);
352
            } else {
353 1
                $job->setStatus(OcrConstants::STATUS_FAILED);
354 1
                $job->setErrorLog($log);
355 1
                $this->jobMapper->update($job);
356 1
                $this->logger->error($log, ['app' => OcrConstants::APP_NAME]);
357
            }
358
        } catch (Exception $e) {
359
            $this->handleException($e);
360
        }
361 1
    }
362
363
    /**
364
     * Gives a temp file name back depending on the type of the OCR.
365
     * Later in the worker this file is used as an output.
366
     *         
367
     * @return string
368
     */
369 6
    private function getTempFile() {
370 6
            $fileName = $this->phpUtil->tempnamWrapper($this->tempM->getTempBaseDir(), OcrConstants::TEMPFILE_PREFIX);
371 5
            $this->phpUtil->unlinkWrapper($fileName);
372 4
            $fileNameWithPostfix = $fileName . '.pdf';
373 4
            $this->phpUtil->touchWrapper($fileNameWithPostfix);
374 3
            $this->phpUtil->chmodWrapper($fileNameWithPostfix, 0600);
375 2
            $fileName = basename($fileName);
376 2
            return $fileName;
377
    }
378
379
    /**
380
     * Takes care of transforming an incoming finished job into a php readable object.
381
     * 
382
     * @param string $job            
383
     * @throws NotFoundException
384
     * @return mixed
385
     */
386 1
    private function transformJob($job) {
387 1
        $decoded = json_decode($job);
388 1
        if ($decoded !== null && isset($decoded->id)) {
389 1
            return $decoded;
390
        } else {
391
            $this->logger->debug('The finished job retrieved by Redis was corrupt.', ['app' => OcrConstants::APP_NAME]);
392
            throw new NotFoundException('The finished job retrieved by Redis was corrupt.');
393
        }
394
    }
395
396
    /**
397
     * Checks if the given languages are supported or not.
398
     * 
399
     * @param string[] $languages            
400
     * @return boolean
401
     */
402 10
    private function checkForAcceptedLanguages($languages) {
403 10
        $installedLanguages = explode(';', $this->appConfigService->getAppValue('languages'));
404 10
        if (count(array_diff($languages, $installedLanguages)) === 0) {
405 3
            return true;
406
        } else {
407 7
            return false;
408
        }
409
    }
410
411
    /**
412
     * Checks if the process should be initiated without any language specified.
413
     * 
414
     * @param string[] $languages            
415
     * @return boolean
416
     */
417 11
    private function noLanguage($languages) {
418 11
        if (in_array('any', $languages)) {
419 6
            return true;
420
        } else {
421 5
            return false;
422
        }
423
    }
424
425
    /**
426
     * Handle the possible thrown Exceptions from all methods of this class.
427
     * 
428
     * @param Exception $e            
429
     * @throws Exception
430
     * @throws NotFoundException
431
     */
432 12
    private function handleException($e) {
433 12
        $this->logger->logException($e, 
434
                [
435 12
                        'message' => 'Exception during job service function processing',
436
                        'app' => OcrConstants::APP_NAME
437
                ]);
438 12
        throw $e;
439
    }
440
}