Completed
Pull Request — master (#93)
by Janis
07:58
created

JobService::transformJob()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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