Completed
Pull Request — master (#93)
by Janis
11:26
created

JobService::process()   C

Complexity

Conditions 7
Paths 26

Size

Total Lines 41
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 41
rs 6.7272
c 1
b 0
f 0
cc 7
eloc 22
nc 26
nop 2
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
23
/**
24
 * Class JobService
25
 *
26
 * @package OCA\Ocr\Service
27
 */
28
class JobService {
29
	
30
	/**
31
	 *
32
	 * @var ILogger
33
	 */
34
	private $logger;
35
	
36
	/**
37
	 *
38
	 * @var RedisService
39
	 */
40
	private $redisService;
41
	
42
	/**
43
	 *
44
	 * @var OcrJobMapper
45
	 */
46
	private $jobMapper;
47
	
48
	/**
49
	 *
50
	 * @var View
51
	 */
52
	private $view;
53
	
54
	/**
55
	 *
56
	 * @var String
57
	 */
58
	private $userId;
59
	
60
	/**
61
	 *
62
	 * @var IL10N
63
	 */
64
	private $l10n;
65
	
66
	/**
67
	 *
68
	 * @var FileService
69
	 */
70
	private $fileService;
71
	
72
	/**
73
	 *
74
	 * @var ITempManager
75
	 */
76
	private $tempM;
77
	
78
	/**
79
	 *
80
	 * @var AppConfigService
81
	 */
82
	private $appConfigService;
83
	
84
	/**
85
	 * JobService constructor.
86
	 *
87
	 * @param IL10N $l10n        	
88
	 * @param ILogger $logger        	
89
	 * @param string $userId        	
90
	 * @param View $view        	
91
	 * @param RedisService $queueService        	
92
	 * @param OcrJobMapper $mapper        	
93
	 * @param FileService $fileService        	
94
	 * @param AppConfigService $appConfigService        	
95
	 */
96
	public function __construct(IL10N $l10n, ILogger $logger, $userId, View $view, ITempManager $tempManager, RedisService $queueService, OcrJobMapper $mapper, FileService $fileService, AppConfigService $appConfigService) {
97
		$this->logger = $logger;
98
		$this->redisService = $queueService;
99
		$this->jobMapper = $mapper;
100
		$this->view = $view;
101
		$this->userId = $userId;
102
		$this->l10n = $l10n;
103
		$this->fileService = $fileService;
104
		$this->tempM = $tempManager;
105
		$this->appConfigService = $appConfigService;
106
	}
107
	
108
	/**
109
	 * Processes and prepares the files for OCR.
110
	 * Sends the stuff to the client in order to OCR async.
111
	 *
112
	 * @param string[] $languages        	
113
	 * @param array $files        	
114
	 * @return string
115
	 */
116
	public function process($languages, $files) {
117
		try {
118
			$this->logger->debug ( 'Will now process files: ' . json_encode ( $files ) . ' with languages: ' . json_encode ( $languages ), [ 
119
					'app' => 'ocr' 
120
			] );
121
			// Check if files and language not empty
122
			$noLang = $this->noLanguage ( $languages );
123
			if (! empty ( $files ) && ($this->checkForAcceptedLanguages ( $languages ) || $noLang)) {
124
				// language part:
125
				if ($noLang) {
126
					$languages = [ ];
127
				}
128
				// file part:
129
				$fileInfo = $this->fileService->buildFileInfo ( $files );
130
				foreach ( $fileInfo as $fInfo ) {
0 ignored issues
show
Bug introduced by
The expression $fileInfo of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
131
					// Check Shared
132
					$shared = $this->fileService->checkSharedWithInitiator ( $fInfo );
133
					$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 132 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...
134
					$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 132 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...
135
					
136
					// set the running type
137
					$fType = $this->fileService->getCorrectType ( $fInfo );
138
					
139
					// create a temp file for ocr processing purposes
140
					$tempFile = $this->getTempFile ( $fType );
141
					
142
					// Create job object
143
					$job = new OcrJob ( OcrConstants::STATUS_PENDING, $source, $target, $tempFile, $fType, $this->userId, false, $fInfo->getName (), null );
144
					
145
					// Init client and send task / job
146
					// Feed the worker
147
					$this->redisService->sendJob ( $job, $languages, \OC::$SERVERROOT );
148
				}
149
				return 'PROCESSING';
150
			} else {
151
				throw new NotFoundException ( $this->l10n->t ( 'Empty parameters passed.' ) );
152
			}
153
		} 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...
154
			$this->handleException ( $e );
155
		}
156
	}
157
	
158
	/**
159
	 * Delete an ocr job for a given id and userId.
160
	 *
161
	 * @param
162
	 *        	$jobId
163
	 * @param string $userId        	
164
	 * @return OcrJob
165
	 */
166
	public function deleteJob($jobId, $userId) {
167
		try {
168
			$job = $this->jobMapper->find ( $jobId );
169
			if ($job->getUserId () !== $userId) {
170
				throw new NotFoundException ( $this->l10n->t ( 'Cannot delete. Wrong owner.' ) );
171
			} else {
172
				$job = $this->jobMapper->delete ( $job );
173
			}
174
			// TODO: return sanitized job for the personal settings page (no information about the source and target and such things.
175
			return $job;
176
		} 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...
177
			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...
178
				$ex = new NotFoundException ( $this->l10n->t ( 'Cannot delete. Wrong ID.' ) );
179
				$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...
180
			} else {
181
				$this->handleException ( $e );
182
			}
183
		}
184
	}
185
	
186
	/**
187
	 * Gets all job objects for a specific user.
188
	 * TODO: use the mapping of the zettel
189
	 *
190
	 * @param string $userId        	
191
	 * @return array
192
	 */
193
	public function getAllJobsForUser($userId) {
194
		try {
195
			$jobs = $this->jobMapper->findAll ( $userId );
196
			$jobsNew = array ();
197
			for($x = 0; $x < count ( $jobs ); $x ++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
198
				$newName = $this->fileService->removeFileExtension ( $jobs [$x] );
199
				$jobs [$x]->setTarget ( $newName );
200
				$jobs [$x]->setSource ( null );
201
				$jobs [$x]->setTempFile ( null );
202
				$jobs [$x]->setType ( null );
203
				$jobs [$x]->setErrorDisplayed ( null );
204
				array_push ( $jobsNew, $jobs [$x] );
205
			}
206
			return $jobsNew;
207
		} 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...
208
			$this->handleException ( $e );
209
		}
210
	}
211
	
212
	/**
213
	 * TODO: finish the job
214
	 * The function the worker will call in order to set the jobs status.
215
	 * The worker should call it automatically after each processing step.
216
	 *
217
	 * @param integer $jobId        	
218
	 * @param boolean $failed        	
219
	 * @param string $errorMessage        	
220
	 */
221
	public function jobFinished($jobId, $failed, $errorMessage) {
222
		try {
223
			$job = $this->jobMapper->find ( $jobId );
224
			if (! $failed) {
225
				$job->setStatus ( OcrConstants::STATUS_PROCESSED );
226
				$this->jobMapper->update ( $job );
227
			} else {
228
				$job->setStatus ( OcrConstants::STATUS_FAILED );
229
				// TODO: add error message log and so on
230
				$this->jobMapper->update ( $job );
231
				$this->logger->error ( $errorMessage, [ 
232
						'app' => 'ocr' 
233
				] );
234
			}
235
		} 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...
236
			$this->handleException ( $e );
237
		}
238
	}
239
	
240
	/**
241
	 * Finishes all Processed files by copying them to the right path and deleteing the temp files.
242
	 * Returns the number of processed files.
243
	 * TODO: adjust behaviour for saving of the worker with the right file extension or not
244
	 *
245
	 * @return array
246
	 */
247
	public function handleProcessed() {
248
		try {
249
			$this->logger->debug ( 'Check if files were processed by ocr and if so, put them to the right dirs.', [ 
250
					'app' => 'ocr' 
251
			] );
252
			$processed = $this->jobMapper->findAllProcessed ( $this->userId );
253
			foreach ( $processed as $job ) {
254
				if ($this->fileService->fileExists ( $job->getTempFile () )) {
255
					// Save the tmp file with newname
256
					$this->view->file_put_contents ( $job->getTarget (), $this->fileService->getFileContents ( $job->getTempFile () ) );
257
					$this->jobMapper->delete ( $job );
258
					$this->fileService->execRemove ( $job->getTempFile () );
259
				} else {
260
					$job->setStatus ( 'FAILED' );
261
					// TODO: set log
262
					$this->jobMapper->update ( $job );
263
					throw new NotFoundException ( $this->l10n->t ( 'Temp file does not exist.' ) );
264
				}
265
			}
266
			return $processed;
267
		} 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...
268
			$this->handleException ( $e );
269
		}
270
	}
271
	
272
	/**
273
	 * Handles all failed orders of ocr processing queue and returns the job objects.
274
	 *
275
	 * @return array
276
	 */
277
	public function handleFailed() {
278
		try {
279
			$failed = $this->jobMapper->findAllFailed ( $this->userId );
280
			foreach ( $failed as $job ) {
281
				// clean the tempfile
282
				$this->fileService->execRemove ( $job->getTempFile () );
283
				// set error displayed
284
				$job->setErrorDisplayed ( true );
285
				$this->jobMapper->update ( $job );
286
			}
287
			$this->logger->debug ( 'Following jobs failed: ' . json_encode ( $failed ), [ 
288
					'app' => 'ocr' 
289
			] );
290
			return $failed;
291
		} 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...
292
			$this->handleException ( $e );
293
		}
294
	}
295
	
296
	/**
297
	 * Gives a temp file name back depending on the type of the OCR.
298
	 * Later in the worker this file is used as an output.
299
	 * TODO: create other solution, as the nextcloud tempmanager is not working as expected
300
	 * @param integer $type        	
301
	 * @return string
302
	 */
303
	private function getTempFile($type) {
304
		if ($type == OcrConstants::TESSERACT) {
305
			return $this->tempM->getTemporaryFile('.txt');
306
		} else {
307
			return $this->tempM->getTemporaryFile ();
308
		}
309
	}
310
	
311
	/**
312
	 * Checks if the given languages are supported or not.
313
	 *
314
	 * @param string[] $languages        	
315
	 * @return boolean
316
	 */
317
	private function checkForAcceptedLanguages($languages) {
318
		$installedLanguages = explode ( ';', $this->appConfigService->getAppValue ( 'languages' ) );
319
		if (count ( array_diff ( $languages, $installedLanguages ) ) === 0) {
320
			return true;
321
		} else {
322
			return false;
323
		}
324
	}
325
	
326
	/**
327
	 * Checks if the process should be initiated without any language specified.
328
	 *
329
	 * @param string[] $languages        	
330
	 * @return boolean
331
	 */
332
	private function noLanguage($languages) {
333
		if (in_array ( 'any', $languages )) {
334
			return true;
335
		} else {
336
			return false;
337
		}
338
	}
339
	
340
	/**
341
	 * Handle the possible thrown Exceptions from all methods of this class.
342
	 *
343
	 * @param Exception $e        	
344
	 * @throws Exception
345
	 * @throws NotFoundException
346
	 */
347 View Code Duplication
	private function handleException($e) {
348
		$this->logger->logException ( $e, [ 
349
				'app' => 'ocr',
350
				'message' => 'Exception during job service function processing' 
351
		] );
352
		if ($e instanceof NotFoundException) {
353
			throw new NotFoundException ( $e->getMessage () );
354
		} else {
355
			throw $e;
356
		}
357
	}
358
}