Completed
Pull Request — master (#93)
by Janis
04:38
created

JobService::process()   C

Complexity

Conditions 7
Paths 26

Size

Total Lines 40
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 40
ccs 0
cts 23
cp 0
rs 6.7272
c 1
b 0
f 0
cc 7
eloc 24
nc 26
nop 2
crap 56
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
25
26
/**
27
 * Class JobService
28
 * 
29
 * @package OCA\Ocr\Service
30
 */
31
class JobService {
32
33
    /**
34
     *
35
     * @var ILogger
36
     */
37
    private $logger;
38
39
    /**
40
     *
41
     * @var RedisService
42
     */
43
    private $redisService;
44
45
    /**
46
     *
47
     * @var OcrJobMapper
48
     */
49
    private $jobMapper;
50
51
    /**
52
     *
53
     * @var View
54
     */
55
    private $view;
56
57
    /**
58
     *
59
     * @var String
60
     */
61
    private $userId;
62
63
    /**
64
     *
65
     * @var IL10N
66
     */
67
    private $l10n;
68
69
    /**
70
     *
71
     * @var FileService
72
     */
73
    private $fileService;
74
75
    /**
76
     *
77
     * @var ITempManager
78
     */
79
    private $tempM;
80
81
    /**
82
     *
83
     * @var AppConfigService
84
     */
85
    private $appConfigService;
86
87
    /**
88
     *
89
     * @var PHPUtil
90
     */
91
    private $phpUtil;
92
93
    /**
94
     * JobService constructor.
95
     * 
96
     * @param IL10N $l10n            
97
     * @param ILogger $logger            
98
     * @param string $userId            
99
     * @param View $view            
100
     * @param RedisService $queueService            
101
     * @param OcrJobMapper $mapper            
102
     * @param FileService $fileService            
103
     * @param AppConfigService $appConfigService            
104
     * @param PHPUtil $phpUtil            
105
     */
106 4
    public function __construct(IL10N $l10n, ILogger $logger, $userId, View $view, ITempManager $tempManager, 
107
            RedisService $queueService, OcrJobMapper $mapper, FileService $fileService, 
108
            AppConfigService $appConfigService, PHPUtil $phpUtil) {
109 4
        $this->logger = $logger;
110 4
        $this->redisService = $queueService;
111 4
        $this->jobMapper = $mapper;
112 4
        $this->view = $view;
113 4
        $this->userId = $userId;
114 4
        $this->l10n = $l10n;
115 4
        $this->fileService = $fileService;
116 4
        $this->tempM = $tempManager;
117 4
        $this->appConfigService = $appConfigService;
118 4
        $this->phpUtil = $phpUtil;
119 4
    }
120
121
    /**
122
     * Processes and prepares the files for OCR.
123
     * Sends the stuff to the client in order to OCR async.
124
     * 
125
     * @param string[] $languages            
126
     * @param array $files            
127
     * @return string
128
     */
129
    public function process($languages, $files) {
130
        try {
131
            $this->logger->debug('Will now process files: {files} with languages: {languages}', 
132
                    [
133
                            'files' => json_encode($files),
134
                            'languages' => json_encode($languages)
135
                    ]);
136
            // Check if files and language not empty
137
            $noLang = $this->noLanguage($languages);
138
            if (!empty($files) && ($this->checkForAcceptedLanguages($languages) || $noLang)) {
139
                // language part:
140
                if ($noLang) {
141
                    $languages = [];
142
                }
143
                // file part:
144
                $fileInfo = $this->fileService->buildFileInfo($files);
145
                foreach ($fileInfo as $fInfo) {
146
                    // Check Shared
147
                    $shared = $this->fileService->checkSharedWithInitiator($fInfo);
148
                    $target = $this->fileService->buildTarget($fInfo, $shared);
149
                    $source = $this->fileService->buildSource($fInfo, $shared);
150
                    // set the running type
151
                    $fType = $this->fileService->getCorrectType($fInfo);
152
                    // create a temp file for ocr processing purposes
153
                    $tempFile = $this->getTempFile($fType);
154
                    // Create job object
155
                    $job = new OcrJob(OcrConstants::STATUS_PENDING, $source, $target, $tempFile, $fType, $this->userId, 
156
                            false, $fInfo->getName(), null);
1 ignored issue
show
Bug introduced by
Consider using $fInfo->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
157
                    // Init client and send task / job
158
                    // Feed the worker
159
                    $this->redisService->sendJob($job, $languages);
160
                }
161
                return 'PROCESSING';
162
            } else {
163
                throw new NotFoundException($this->l10n->t('Empty parameters passed.'));
164
            }
165
        } catch (Exception $e) {
166
            $this->handleException($e);
167
        }
168
    }
169
170
    /**
171
     * Delete an ocr job for a given id and userId.
172
     * 
173
     * @param
174
     *            $jobId
175
     * @param string $userId            
176
     * @return OcrJob
177
     */
178
    public function deleteJob($jobId, $userId) {
179
        try {
180
            $job = $this->jobMapper->find($jobId);
181
            if ($job->getUserId() !== $userId) {
182
                throw new NotFoundException($this->l10n->t('Cannot delete. Wrong owner.'));
183
            } else {
184
                $job = $this->jobMapper->delete($job);
185
            }
186
            $job->setTarget(null);
187
            $job->setSource(null);
188
            $job->setStatus(null);
189
            $job->setTempFile(null);
190
            $job->setType(null);
191
            $job->setUserId(null);
192
            $job->setErrorDisplayed(null);
193
            return $job;
194
        } catch (Exception $e) {
195
            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...
196
                $ex = new NotFoundException($this->l10n->t('Cannot delete. Wrong ID.'));
197
                $this->handleException($ex);
198
            } else {
199
                $this->handleException($e);
200
            }
201
        }
202
    }
203
204
    /**
205
     * Gets all job objects for a specific user.
206
     * 
207
     * @param string $userId            
208
     * @return array
209
     */
210
    public function getAllJobsForUser($userId) {
211
        try {
212
            $jobs = $this->jobMapper->findAll($userId);
213
            $jobsNew = array();
214
            foreach ($jobs as $job) {
215
                $job->setTarget(null);
216
                $job->setSource(null);
217
                $job->setTempFile(null);
218
                $job->setType(null);
219
                $job->setUserId(null);
220
                $job->setErrorDisplayed(null);
221
                array_push($jobsNew, $job);
222
            }
223
            return $jobsNew;
224
        } catch (Exception $e) {
225
            $this->handleException($e);
226
        }
227
    }
228
229
    /**
230
     * The function checks if there are finished jobs to process finally.
231
     * 
232
     * @throws NotFoundException
233
     */
234
    public function checkForFinishedJobs() {
235
        try {
236
            $finishedJobs = $this->redisService->readingFinishedJobs();
237
            foreach ($finishedJobs as $finishedJob) {
238
                $fJob = $this->transformJob($finishedJob);
239
                $this->logger->debug('The following job finished: {job}', 
240
                        [
241
                                'job' => $fJob
242
                        ]);
243
                $this->jobFinished($fJob->id, $fJob->error, $fJob->log);
244
            }
245
        } catch (Exception $e) {
246
            throw new NotFoundException($this->l10n->t('Reading the finished jobs from redis went wrong.'));
247
        }
248
    }
249
250
    /**
251
     * The function the worker will call in order to set the jobs status.
252
     * The worker should call it automatically after each processing step.
253
     * 
254
     * @param integer $jobId            
255
     * @param boolean $error            
256
     * @param string $log            
257
     */
258
    public function jobFinished($jobId, $error, $log) {
259
        try {
260
            $job = $this->jobMapper->find($jobId);
261
            if (!$error) {
262
                $job->setStatus(OcrConstants::STATUS_PROCESSED);
263
                $this->jobMapper->update($job);
264
            } else {
265
                $job->setStatus(OcrConstants::STATUS_FAILED);
266
                $job->setErrorLog($log);
267
                $this->jobMapper->update($job);
268
                $this->logger->error($log);
269
            }
270
        } catch (Exception $e) {
271
            $this->handleException($e);
272
        }
273
    }
274
275
    /**
276
     * Finishes all Processed files by copying them to the right path and deleteing the temp files.
277
     * Returns the number of processed files.
278
     * 
279
     * @return array
280
     */
281
    public function handleProcessed() {
282
        try {
283
            $this->logger->debug('Check if files were processed by ocr and if so, put them to the right dirs.');
284
            $processed = $this->jobMapper->findAllProcessed($this->userId);
285
            foreach ($processed as $job) {
286
                if ($this->fileService->fileExists($job->getTempFile())) {
287
                    // Save the tmp file with newname
288
                    $this->view->file_put_contents($job->getTarget(), 
289
                            $this->fileService->getFileContents($job->getTempFile()));
290
                    $this->jobMapper->delete($job);
291
                    $this->fileService->execRemove($job->getTempFile());
292
                } else {
293
                    $job->setStatus(OcrConstants::STATUS_FAILED);
294
                    $job->setErrorLog('Temp file does not exist.');
295
                    $this->jobMapper->update($job);
296
                    throw new NotFoundException($this->l10n->t('Temp file does not exist.'));
297
                }
298
            }
299
            return $processed;
300
        } catch (Exception $e) {
301
            $this->handleException($e);
302
        }
303
    }
304
305
    /**
306
     * Handles all failed orders of ocr processing queue and returns the job objects.
307
     * 
308
     * @return array
309
     */
310
    public function handleFailed() {
311
        try {
312
            $failed = $this->jobMapper->findAllFailed($this->userId);
313
            foreach ($failed as $job) {
314
                // clean the tempfile
315
                $this->fileService->execRemove($job->getTempFile());
316
                // set error displayed
317
                $job->setErrorDisplayed(true);
318
                $this->jobMapper->update($job);
319
            }
320
            $this->logger->debug('Following jobs failed: {failed}', 
321
                    [
322
                            'failed' => json_encode($failed)
323
                    ]);
324
            return $failed;
325
        } catch (Exception $e) {
326
            $this->handleException($e);
327
        }
328
    }
329
330
    /**
331
     * Gives a temp file name back depending on the type of the OCR.
332
     * Later in the worker this file is used as an output.
333
     * 
334
     * @param integer $type            
335
     * @return string
336
     */
337
    private function getTempFile($type) {
338
        if ($type === OcrConstants::TESSERACT) {
339
            $fileName = $this->phpUtil->tempnamWrapper($this->tempM->getTempBaseDir(), OcrConstants::TEMPFILE_PREFIX);
340
            $this->phpUtil->unlinkWrapper($fileName);
341
            $fileNameWithPostfix = $fileName . '.txt';
342
            $this->phpUtil->touchWrapper($fileNameWithPostfix);
343
            $this->phpUtil->chmodWrapper($fileNameWithPostfix, 0600);
344
            return $fileNameWithPostfix;
345
        } else {
346
            return $this->phpUtil->tempnamWrapper($this->tempM->getTempBaseDir(), 'ocr_');
347
        }
348
    }
349
350
    /**
351
     * Takes care of transforming an incoming finished job into a php readable object.
352
     * 
353
     * @param string $job            
354
     * @throws NotFoundException
355
     * @return mixed
356
     */
357
    private function transformJob($job) {
358
        $decoded = json_decode($job);
359
        if ($decoded !== null && isset($decoded->id)) {
360
            return $decoded;
361
        } else {
362
            throw new NotFoundException($this->l10n->t('The finished job retrieved by Redis was corrupt.'));
363
        }
364
    }
365
366
    /**
367
     * Checks if the given languages are supported or not.
368
     * 
369
     * @param string[] $languages            
370
     * @return boolean
371
     */
372
    private function checkForAcceptedLanguages($languages) {
373
        $installedLanguages = explode(';', $this->appConfigService->getAppValue('languages'));
374
        if (count(array_diff($languages, $installedLanguages)) === 0) {
375
            return true;
376
        } else {
377
            return false;
378
        }
379
    }
380
381
    /**
382
     * Checks if the process should be initiated without any language specified.
383
     * 
384
     * @param string[] $languages            
385
     * @return boolean
386
     */
387
    private function noLanguage($languages) {
388
        if (in_array('any', $languages)) {
389
            return true;
390
        } else {
391
            return false;
392
        }
393
    }
394
395
    /**
396
     * Handle the possible thrown Exceptions from all methods of this class.
397
     * 
398
     * @param Exception $e            
399
     * @throws Exception
400
     * @throws NotFoundException
401
     */
402
    private function handleException($e) {
403
        $this->logger->logException($e, 
404
                [
405
                        'message' => 'Exception during job service function processing'
406
                ]);
407
        throw $e;
408
    }
409
}