Completed
Pull Request — master (#93)
by Janis
14:36
created

JobService::deleteJob()   B

Complexity

Conditions 4
Paths 23

Size

Total Lines 25
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 25
rs 8.5806
cc 4
eloc 21
nc 23
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: {files} with languages: {languages}', [ 
119
					'files' => json_encode ( $files ),
120
					'languages' => json_encode ( $languages )
121
			] );
122
			// Check if files and language not empty
123
			$noLang = $this->noLanguage ( $languages );
124
			if (! empty ( $files ) && ($this->checkForAcceptedLanguages ( $languages ) || $noLang)) {
125
				// language part:
126
				if ($noLang) {
127
					$languages = [ ];
128
				}
129
				// file part:
130
				$fileInfo = $this->fileService->buildFileInfo ( $files );
131
				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...
132
					// Check Shared
133
					$shared = $this->fileService->checkSharedWithInitiator ( $fInfo );
134
					$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 133 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...
135
					$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 133 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...
136
					
137
					// set the running type
138
					$fType = $this->fileService->getCorrectType ( $fInfo );
139
					
140
					// create a temp file for ocr processing purposes
141
					$tempFile = $this->getTempFile ( $fType );
142
					
143
					// Create job object
144
					$job = new OcrJob ( OcrConstants::STATUS_PENDING, $source, $target, $tempFile, $fType, $this->userId, false, $fInfo->getName (), null );
145
					
146
					// Init client and send task / job
147
					// Feed the worker
148
					$this->redisService->sendJob ( $job, $languages, \OC::$SERVERROOT );
149
				}
150
				return 'PROCESSING';
151
			} else {
152
				throw new NotFoundException ( $this->l10n->t ( 'Empty parameters passed.' ) );
153
			}
154
		} 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...
155
			$this->handleException ( $e );
156
		}
157
	}
158
	
159
	/**
160
	 * Delete an ocr job for a given id and userId.
161
	 *
162
	 * @param
163
	 *        	$jobId
164
	 * @param string $userId        	
165
	 * @return OcrJob
166
	 */
167
	public function deleteJob($jobId, $userId) {
168
		try {
169
			$job = $this->jobMapper->find ( $jobId );
170
			if ($job->getUserId () !== $userId) {
171
				throw new NotFoundException ( $this->l10n->t ( 'Cannot delete. Wrong owner.' ) );
172
			} else {
173
				$job = $this->jobMapper->delete ( $job );
174
			}
175
			$job->setTarget ( null );
176
			$job->setSource ( null );
177
			$job->setStatus ( null );
178
			$job->setTempFile ( null );
179
			$job->setType ( null );
180
			$job->setUserId ( null );
181
			$job->setErrorDisplayed ( null );
182
			return $job;
183
		} 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...
184
			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...
185
				$ex = new NotFoundException ( $this->l10n->t ( 'Cannot delete. Wrong ID.' ) );
186
				$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...
187
			} else {
188
				$this->handleException ( $e );
189
			}
190
		}
191
	}
192
	
193
	/**
194
	 * Gets all job objects for a specific user.
195
	 *
196
	 * @param string $userId        	
197
	 * @return array
198
	 */
199
	public function getAllJobsForUser($userId) {
200
		try {
201
			$jobs = $this->jobMapper->findAll ( $userId );
202
			$jobsNew = array ();
203
			foreach ( $jobs as $job ) {
204
				$job->setTarget ( null );
205
				$job->setSource ( null );
206
				$job->setTempFile ( null );
207
				$job->setType ( null );
208
				$job->setUserId ( null );
209
				$job->setErrorDisplayed ( null );
210
				
211
				array_push ( $jobsNew, $job );
212
			}
213
			return $jobsNew;
214
		} 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...
215
			$this->handleException ( $e );
216
		}
217
	}
218
	
219
	/**
220
	 *
221
	 * @throws NotFoundException
222
	 */
223
	public function checkForFinishedJobs() {
224
		try {
225
			$finishedJobs = $this->redisService->readingFinishedJobs ();
226
			foreach ( $finishedJobs as $finishedJob ) {
227
				$fJob = json_decode ( $finishedJob );
228
				$this->logger->debug('The following job finished: {job}', ['job' => $fJob]);
229
				$this->jobFinished ( $fJob->id, $fJob->error, $fJob->log );
230
			}
231
		} 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...
232
			throw new NotFoundException ( $this->l10n->t ( 'Reading the finished jobs from redis went wrong.' ) );
233
		}
234
	}
235
	
236
	/**
237
	 * The function the worker will call in order to set the jobs status.
238
	 * The worker should call it automatically after each processing step.
239
	 *
240
	 * @param integer $jobId        	
241
	 * @param boolean $error        	
242
	 * @param string $log        	
243
	 */
244
	public function jobFinished($jobId, $error, $log) {
245
		try {
246
			$job = $this->jobMapper->find ( $jobId );
247
			if (! $error) {
248
				$job->setStatus ( OcrConstants::STATUS_PROCESSED );
249
				$this->jobMapper->update ( $job );
250
			} else {
251
				$job->setStatus ( OcrConstants::STATUS_FAILED );
252
				$job->setErrorLog( $log );
253
				$this->jobMapper->update ( $job );
254
				$this->logger->error ( $log);
255
			}
256
		} 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...
257
			$this->handleException ( $e );
258
		}
259
	}
260
	
261
	/**
262
	 * Finishes all Processed files by copying them to the right path and deleteing the temp files.
263
	 * Returns the number of processed files.
264
	 *
265
	 * @return array
266
	 */
267
	public function handleProcessed() {
268
		try {
269
			$this->logger->debug ( 'Check if files were processed by ocr and if so, put them to the right dirs.');
270
			$processed = $this->jobMapper->findAllProcessed ( $this->userId );
271
			foreach ( $processed as $job ) {
272
				if ($this->fileService->fileExists ( $job->getTempFile () )) {
273
					// Save the tmp file with newname
274
					$this->view->file_put_contents ( $job->getTarget (), $this->fileService->getFileContents ( $job->getTempFile () ) );
275
					$this->jobMapper->delete ( $job );
276
					$this->fileService->execRemove ( $job->getTempFile () );
277
				} else {
278
					$job->setStatus ( OcrConstants::STATUS_FAILED );
279
					$job->setErrorLog('Temp file does not exist.');
280
					$this->jobMapper->update ( $job );
281
					throw new NotFoundException ( $this->l10n->t ( 'Temp file does not exist.' ) );
282
				}
283
			}
284
			return $processed;
285
		} 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...
286
			$this->handleException ( $e );
287
		}
288
	}
289
	
290
	/**
291
	 * Handles all failed orders of ocr processing queue and returns the job objects.
292
	 *
293
	 * @return array
294
	 */
295
	public function handleFailed() {
296
		try {
297
			$failed = $this->jobMapper->findAllFailed ( $this->userId );
298
			foreach ( $failed as $job ) {
299
				// clean the tempfile
300
				$this->fileService->execRemove ( $job->getTempFile () );
301
				// set error displayed
302
				$job->setErrorDisplayed ( true );
303
				$this->jobMapper->update ( $job );
304
			}
305
			$this->logger->debug ( 'Following jobs failed: {failed}', [ 
306
					'failed' => json_encode ( $failed )
307
			] );
308
			return $failed;
309
		} 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...
310
			$this->handleException ( $e );
311
		}
312
	}
313
	
314
	/**
315
	 * Gives a temp file name back depending on the type of the OCR.
316
	 * Later in the worker this file is used as an output.
317
	 * TODO: outsource the php functions to util and handle exceptions accordingly
318
	 *
319
	 * @param integer $type        	
320
	 * @return string
321
	 */
322
	private function getTempFile($type) {
323
		if ($type == OcrConstants::TESSERACT) {
324
			$fileName = tempnam ( $this->tempM->getTempBaseDir (), 'ocr_' );
325
			unlink ( $fileName );
326
			$fileNameWithPostfix = $fileName . '.txt';
327
			touch ( $fileNameWithPostfix );
328
			chmod ( $fileNameWithPostfix, 0600 );
329
			return $fileNameWithPostfix;
330
		} else {
331
			return tempnam ( $this->tempM->getTempBaseDir (), 'ocr_' );
332
		}
333
	}
334
	
335
	/**
336
	 * Checks if the given languages are supported or not.
337
	 *
338
	 * @param string[] $languages        	
339
	 * @return boolean
340
	 */
341
	private function checkForAcceptedLanguages($languages) {
342
		$installedLanguages = explode ( ';', $this->appConfigService->getAppValue ( 'languages' ) );
343
		if (count ( array_diff ( $languages, $installedLanguages ) ) === 0) {
344
			return true;
345
		} else {
346
			return false;
347
		}
348
	}
349
	
350
	/**
351
	 * Checks if the process should be initiated without any language specified.
352
	 *
353
	 * @param string[] $languages        	
354
	 * @return boolean
355
	 */
356
	private function noLanguage($languages) {
357
		if (in_array ( 'any', $languages )) {
358
			return true;
359
		} else {
360
			return false;
361
		}
362
	}
363
	
364
	/**
365
	 * Handle the possible thrown Exceptions from all methods of this class.
366
	 *
367
	 * @param Exception $e        	
368
	 * @throws Exception
369
	 * @throws NotFoundException
370
	 */
371 View Code Duplication
	private function handleException($e) {
372
		$this->logger->logException ( $e, [ 
373
				'app' => 'ocr',
374
				'message' => 'Exception during job service function processing' 
375
		] );
376
		if ($e instanceof NotFoundException) {
377
			throw new NotFoundException ( $e->getMessage () );
378
		} else {
379
			throw $e;
380
		}
381
	}
382
}