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

JobService   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 379
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 8.16%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 38
lcom 1
cbo 8
dl 0
loc 379
ccs 12
cts 147
cp 0.0816
rs 8.3999
c 1
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 1
C process() 0 40 7
B deleteJob() 0 25 4
A getAllJobsForUser() 0 18 3
A checkForFinishedJobs() 0 15 3
A jobFinished() 0 16 3
B handleProcessed() 0 23 4
A handleFailed() 0 19 3
A getTempFile() 0 12 2
A transformJob() 0 8 3
A checkForAcceptedLanguages() 0 8 2
A noLanguage() 0 7 2
A handleException() 0 7 1
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\Db\File;
21
use OCA\Ocr\Constants\OcrConstants;
22
use OCA\Ocr\Util\PHPUtil;
23
24
25
/**
26
 * Class JobService
27
 * 
28
 * @package OCA\Ocr\Service
29
 */
30
class JobService {
31
32
    /**
33
     *
34
     * @var ILogger
35
     */
36
    private $logger;
37
38
    /**
39
     *
40
     * @var RedisService
41
     */
42
    private $redisService;
43
44
    /**
45
     *
46
     * @var OcrJobMapper
47
     */
48
    private $jobMapper;
49
50
    /**
51
     *
52
     * @var View
53
     */
54
    private $view;
55
56
    /**
57
     *
58
     * @var String
59
     */
60
    private $userId;
61
62
    /**
63
     *
64
     * @var IL10N
65
     */
66
    private $l10n;
67
68
    /**
69
     *
70
     * @var FileService
71
     */
72
    private $fileService;
73
74
    /**
75
     *
76
     * @var ITempManager
77
     */
78
    private $tempM;
79
80
    /**
81
     *
82
     * @var AppConfigService
83
     */
84
    private $appConfigService;
85
86
    /**
87
     *
88
     * @var PHPUtil
89
     */
90
    private $phpUtil;
91
92
    /**
93
     * JobService constructor.
94
     * 
95
     * @param IL10N $l10n            
96
     * @param ILogger $logger            
97
     * @param string $userId            
98
     * @param View $view            
99
     * @param RedisService $queueService            
100
     * @param OcrJobMapper $mapper            
101
     * @param FileService $fileService            
102
     * @param AppConfigService $appConfigService            
103
     * @param PHPUtil $phpUtil            
104
     */
105 4
    public function __construct(IL10N $l10n, ILogger $logger, $userId, View $view, ITempManager $tempManager, 
106
            RedisService $queueService, OcrJobMapper $mapper, FileService $fileService, 
107
            AppConfigService $appConfigService, PHPUtil $phpUtil) {
108 4
        $this->logger = $logger;
109 4
        $this->redisService = $queueService;
110 4
        $this->jobMapper = $mapper;
111 4
        $this->view = $view;
112 4
        $this->userId = $userId;
113 4
        $this->l10n = $l10n;
114 4
        $this->fileService = $fileService;
115 4
        $this->tempM = $tempManager;
116 4
        $this->appConfigService = $appConfigService;
117 4
        $this->phpUtil = $phpUtil;
118 4
    }
119
120
    /**
121
     * Processes and prepares the files for OCR.
122
     * Sends the stuff to the client in order to OCR async.
123
     * 
124
     * @param string[] $languages            
125
     * @param array $files            
126
     * @return string
127
     */
128
    public function process($languages, $files) {
129
        try {
130
            $this->logger->debug('Will now process files: {files} with languages: {languages}', 
131
                    [
132
                            'files' => json_encode($files),
133
                            'languages' => json_encode($languages)
134
                    ]);
135
            // Check if files and language not empty
136
            $noLang = $this->noLanguage($languages);
137
            if (!empty($files) && ($this->checkForAcceptedLanguages($languages) || $noLang)) {
138
                // language part:
139
                if ($noLang) {
140
                    $languages = [];
141
                }
142
                // file part:
143
                $fileInfo = $this->fileService->buildFileInfo($files);
144
                foreach ($fileInfo as $fInfo) {
145
                    // Check Shared
146
                    $shared = $this->fileService->checkSharedWithInitiator($fInfo);
147
                    $target = $this->fileService->buildTarget($fInfo, $shared);
0 ignored issues
show
Bug introduced by
It seems like $shared defined by $this->fileService->chec...edWithInitiator($fInfo) on line 146 can also be of type null; however, OCA\Ocr\Service\FileService::buildTarget() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
148
                    $source = $this->fileService->buildSource($fInfo, $shared);
0 ignored issues
show
Bug introduced by
It seems like $shared defined by $this->fileService->chec...edWithInitiator($fInfo) on line 146 can also be of type null; however, OCA\Ocr\Service\FileService::buildSource() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
149
                    // set the running type
150
                    $fType = $this->fileService->getCorrectType($fInfo);
151
                    // create a temp file for ocr processing purposes
152
                    $tempFile = $this->getTempFile($fType);
153
                    // Create job object
154
                    $job = new OcrJob(OcrConstants::STATUS_PENDING, $source, $target, $tempFile, $fType, $this->userId, 
155
                            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...
156
                    // Init client and send task / job
157
                    // Feed the worker
158
                    $this->redisService->sendJob($job, $languages, \OC::$SERVERROOT);
159
                }
160
                return 'PROCESSING';
161
            } else {
162
                throw new NotFoundException($this->l10n->t('Empty parameters passed.'));
163
            }
164
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class OCA\Ocr\Service\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
165
            $this->handleException($e);
166
        }
167
    }
168
169
    /**
170
     * Delete an ocr job for a given id and userId.
171
     * 
172
     * @param
173
     *            $jobId
174
     * @param string $userId            
175
     * @return OcrJob
176
     */
177
    public function deleteJob($jobId, $userId) {
178
        try {
179
            $job = $this->jobMapper->find($jobId);
180
            if ($job->getUserId() !== $userId) {
181
                throw new NotFoundException($this->l10n->t('Cannot delete. Wrong owner.'));
182
            } else {
183
                $job = $this->jobMapper->delete($job);
184
            }
185
            $job->setTarget(null);
186
            $job->setSource(null);
187
            $job->setStatus(null);
188
            $job->setTempFile(null);
189
            $job->setType(null);
190
            $job->setUserId(null);
191
            $job->setErrorDisplayed(null);
192
            return $job;
193
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class OCA\Ocr\Service\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
194
            if ($e instanceof DoesNotExistException) {
0 ignored issues
show
Bug introduced by
The class OCA\Ocr\Service\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...
195
                $ex = new NotFoundException($this->l10n->t('Cannot delete. Wrong ID.'));
196
                $this->handleException($ex);
0 ignored issues
show
Documentation introduced by
$ex is of type object<OCA\Ocr\Service\NotFoundException>, but the function expects a object<OCA\Ocr\Service\Exception>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
197
            } else {
198
                $this->handleException($e);
199
            }
200
        }
201
    }
202
203
    /**
204
     * Gets all job objects for a specific user.
205
     * 
206
     * @param string $userId            
207
     * @return array
208
     */
209
    public function getAllJobsForUser($userId) {
210
        try {
211
            $jobs = $this->jobMapper->findAll($userId);
212
            $jobsNew = array();
213
            foreach ($jobs as $job) {
214
                $job->setTarget(null);
215
                $job->setSource(null);
216
                $job->setTempFile(null);
217
                $job->setType(null);
218
                $job->setUserId(null);
219
                $job->setErrorDisplayed(null);
220
                array_push($jobsNew, $job);
221
            }
222
            return $jobsNew;
223
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class OCA\Ocr\Service\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
224
            $this->handleException($e);
225
        }
226
    }
227
228
    /**
229
     * The function checks if there are finished jobs to process finally.
230
     * 
231
     * @throws NotFoundException
232
     */
233
    public function checkForFinishedJobs() {
234
        try {
235
            $finishedJobs = $this->redisService->readingFinishedJobs();
236
            foreach ($finishedJobs as $finishedJob) {
237
                $fJob = $this->transformJob($finishedJob);
238
                $this->logger->debug('The following job finished: {job}', 
239
                        [
240
                                'job' => $fJob
241
                        ]);
242
                $this->jobFinished($fJob->id, $fJob->error, $fJob->log);
243
            }
244
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class OCA\Ocr\Service\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
245
            throw new NotFoundException($this->l10n->t('Reading the finished jobs from redis went wrong.'));
246
        }
247
    }
248
249
    /**
250
     * The function the worker will call in order to set the jobs status.
251
     * The worker should call it automatically after each processing step.
252
     * 
253
     * @param integer $jobId            
254
     * @param boolean $error            
255
     * @param string $log            
256
     */
257
    public function jobFinished($jobId, $error, $log) {
258
        try {
259
            $job = $this->jobMapper->find($jobId);
260
            if (!$error) {
261
                $job->setStatus(OcrConstants::STATUS_PROCESSED);
262
                $this->jobMapper->update($job);
263
            } else {
264
                $job->setStatus(OcrConstants::STATUS_FAILED);
265
                $job->setErrorLog($log);
266
                $this->jobMapper->update($job);
267
                $this->logger->error($log);
268
            }
269
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class OCA\Ocr\Service\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
270
            $this->handleException($e);
271
        }
272
    }
273
274
    /**
275
     * Finishes all Processed files by copying them to the right path and deleteing the temp files.
276
     * Returns the number of processed files.
277
     * 
278
     * @return array
279
     */
280
    public function handleProcessed() {
281
        try {
282
            $this->logger->debug('Check if files were processed by ocr and if so, put them to the right dirs.');
283
            $processed = $this->jobMapper->findAllProcessed($this->userId);
284
            foreach ($processed as $job) {
285
                if ($this->fileService->fileExists($job->getTempFile())) {
286
                    // Save the tmp file with newname
287
                    $this->view->file_put_contents($job->getTarget(), 
288
                            $this->fileService->getFileContents($job->getTempFile()));
289
                    $this->jobMapper->delete($job);
290
                    $this->fileService->execRemove($job->getTempFile());
291
                } else {
292
                    $job->setStatus(OcrConstants::STATUS_FAILED);
293
                    $job->setErrorLog('Temp file does not exist.');
294
                    $this->jobMapper->update($job);
295
                    throw new NotFoundException($this->l10n->t('Temp file does not exist.'));
296
                }
297
            }
298
            return $processed;
299
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class OCA\Ocr\Service\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
300
            $this->handleException($e);
301
        }
302
    }
303
304
    /**
305
     * Handles all failed orders of ocr processing queue and returns the job objects.
306
     * 
307
     * @return array
308
     */
309
    public function handleFailed() {
310
        try {
311
            $failed = $this->jobMapper->findAllFailed($this->userId);
312
            foreach ($failed as $job) {
313
                // clean the tempfile
314
                $this->fileService->execRemove($job->getTempFile());
315
                // set error displayed
316
                $job->setErrorDisplayed(true);
317
                $this->jobMapper->update($job);
318
            }
319
            $this->logger->debug('Following jobs failed: {failed}', 
320
                    [
321
                            'failed' => json_encode($failed)
322
                    ]);
323
            return $failed;
324
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class OCA\Ocr\Service\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
325
            $this->handleException($e);
326
        }
327
    }
328
329
    /**
330
     * Gives a temp file name back depending on the type of the OCR.
331
     * Later in the worker this file is used as an output.
332
     * 
333
     * @param integer $type            
334
     * @return string
335
     */
336
    private function getTempFile($type) {
337
        if ($type === OcrConstants::TESSERACT) {
338
            $fileName = $this->phpUtil->tempnamWrapper($this->tempM->getTempBaseDir(), OcrConstants::TEMPFILE_PREFIX);
339
            $this->phpUtil->unlinkWrapper($fileName);
340
            $fileNameWithPostfix = $fileName . '.txt';
341
            $this->phpUtil->touchWrapper($fileNameWithPostfix);
342
            $this->phpUtil->chmodWrapper($fileNameWithPostfix, 0600);
343
            return $fileNameWithPostfix;
344
        } else {
345
            return $this->phpUtil->tempnamWrapper($this->tempM->getTempBaseDir(), 'ocr_');
346
        }
347
    }
348
349
    /**
350
     * Takes care of transforming an incoming finished job into a php readable object.
351
     * 
352
     * @param string $job            
353
     * @throws NotFoundException
354
     * @return mixed
355
     */
356
    private function transformJob($job) {
357
        $decoded = json_decode($job);
358
        if ($decoded !== null && isset($decoded->id)) {
359
            return $decoded;
360
        } else {
361
            throw new NotFoundException($this->l10n->t('The finished job retrieved by Redis was corrupt.'));
362
        }
363
    }
364
365
    /**
366
     * Checks if the given languages are supported or not.
367
     * 
368
     * @param string[] $languages            
369
     * @return boolean
370
     */
371
    private function checkForAcceptedLanguages($languages) {
372
        $installedLanguages = explode(';', $this->appConfigService->getAppValue('languages'));
373
        if (count(array_diff($languages, $installedLanguages)) === 0) {
374
            return true;
375
        } else {
376
            return false;
377
        }
378
    }
379
380
    /**
381
     * Checks if the process should be initiated without any language specified.
382
     * 
383
     * @param string[] $languages            
384
     * @return boolean
385
     */
386
    private function noLanguage($languages) {
387
        if (in_array('any', $languages)) {
388
            return true;
389
        } else {
390
            return false;
391
        }
392
    }
393
394
    /**
395
     * Handle the possible thrown Exceptions from all methods of this class.
396
     * 
397
     * @param Exception $e            
398
     * @throws Exception
399
     * @throws NotFoundException
400
     */
401
    private function handleException($e) {
402
        $this->logger->logException($e, 
403
                [
404
                        'message' => 'Exception during job service function processing'
405
                ]);
406
        throw $e;
407
    }
408
}