Completed
Pull Request — master (#93)
by Janis
13:30
created

JobService::process()   C

Complexity

Conditions 7
Paths 26

Size

Total Lines 42
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 42
rs 6.7272
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
22
/**
23
 * Class JobService
24
 *
25
 * @package OCA\Ocr\Service
26
 */
27
class JobService {
28
	
29
	/**
30
	 *
31
	 * @var ILogger
32
	 */
33
	private $logger;
34
	
35
	/**
36
	 *
37
	 * @var QueueService
38
	 */
39
	private $queueService;
40
	
41
	/**
42
	 *
43
	 * @var OcrJobMapper
44
	 */
45
	private $jobMapper;
46
	
47
	/**
48
	 *
49
	 * @var View
50
	 */
51
	private $view;
52
	
53
	/**
54
	 *
55
	 * @var String
56
	 */
57
	private $userId;
58
	
59
	/**
60
	 *
61
	 * @var IL10N
62
	 */
63
	private $l10n;
64
	
65
	/**
66
	 *
67
	 * @var FileService
68
	 */
69
	private $fileService;
70
	
71
	/**
72
	 *
73
	 * @var ITempManager
74
	 */
75
	private $tempM;
76
	
77
	/**
78
	 * JobService constructor.
79
	 *
80
	 * @param IL10N $l10n        	
81
	 * @param ILogger $logger        	
82
	 * @param string $userId        	
83
	 * @param View $view        	
84
	 * @param QueueService $queueService        	
85
	 * @param OcrJobMapper $mapper        	
86
	 * @param FileService $fileService        	
87
	 */
88
	public function __construct(IL10N $l10n, ILogger $logger, $userId, View $view, ITempManager $tempManager, QueueService $queueService, OcrJobMapper $mapper, FileService $fileService) {
89
		$this->logger = $logger;
90
		$this->queueService = $queueService;
91
		$this->jobMapper = $mapper;
92
		$this->view = $view;
93
		$this->userId = $userId;
94
		$this->l10n = $l10n;
95
		$this->fileService = $fileService;
96
		$this->tempM = $tempManager;
97
	}
98
	
99
	/**
100
	 * Processes and prepares the files for OCR.
101
	 * Sends the stuff to the client in order to OCR async.
102
	 *
103
	 * @param string[] $languages        	
104
	 * @param array $files        	
105
	 * @return string
106
	 */
107
	public function process($languages, $files) {
108
		try {
109
			$this->logger->debug ( 'Will now process files: ' . json_encode ( $files ) . ' with languages: ' . json_encode ( $languages ), [ 
110
					'app' => 'ocr' 
111
			] );
112
			// Check if files and language not empty
113
			$noLang = $this->noLanguage ( $languages );
114
			if (! empty ( $files ) && ($this->checkForAcceptedLanguages ( $languages ) || $noLang)) {
115
				// language part:
116
				if ($noLang) {
117
					$languages = [];
118
				}
119
				// file part:
120
				$fileInfo = $this->fileService->buildFileInfo ( $files );
121
				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...
122
					// Check Shared
123
					$shared = $this->fileService->checkSharedWithInitiator ( $fInfo );
124
					$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 123 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...
125
					$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 123 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...
126
					
127
					// create a temp file for ocr processing purposes
128
					$tempFile = $this->tempM->getTemporaryFile ();
129
					
130
					// set the running type
131
					$fType = $this->fileService->getCorrectType ( $fInfo );
132
					
133
					// TODO: create a security token
134
					// Create job object
135
					$job = new OcrJob ( 'PENDING', $source, $target, $tempFile, $fType, $this->userId, false, $fInfo->getName(), null );
136
					
137
					// Init client and send task / job
138
					// Feed the worker
139
					$this->queueService->sendJob ( $job, $languages, \OC::$SERVERROOT );
140
				}
141
				return 'PROCESSING';
142
			} else {
143
				throw new NotFoundException ( $this->l10n->t ( 'Empty parameters passed.' ) );
144
			}
145
		} 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...
146
			$this->handleException ( $e );
147
		}
148
	}
149
	
150
	/**
151
	 * Delete an ocr job for a given id and userId.
152
	 *
153
	 * @param
154
	 *        	$jobId
155
	 * @param string $userId        	
156
	 * @return OcrJob
157
	 */
158
	public function deleteJob($jobId, $userId) {
159
		try {
160
			$job = $this->jobMapper->find ( $jobId );
161
			if ($job->getUserId () !== $userId) {
162
				throw new NotFoundException ( $this->l10n->t ( 'Cannot delete. Wrong owner.' ) );
163
			} else {
164
				$job = $this->jobMapper->delete ( $job );
165
			}
166
			// TODO: return sanitized job for the personal settings page (no information about the source and target and such things.
167
			return $job;
168
		} 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...
169
			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...
170
				$ex = new NotFoundException ( $this->l10n->t ( 'Cannot delete. Wrong ID.' ) );
171
				$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...
172
			} else {
173
				$this->handleException ( $e );
174
			}
175
		}
176
	}
177
	
178
	/**
179
	 * Gets all job objects for a specific user.
180
	 * TODO: use the mapping of the zettel
181
	 *
182
	 * @param string $userId        	
183
	 * @return array
184
	 */
185
	public function getAllJobsForUser($userId) {
186
		try {
187
			$jobs = $this->jobMapper->findAll ( $userId );
188
			$jobsNew = array ();
189
			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...
190
				$newName = $this->fileService->removeFileExtension ( $jobs [$x] );
191
				$jobs [$x]->setTarget ( $newName );
192
				$jobs [$x]->setSource ( null );
193
				$jobs [$x]->setTempFile ( null );
194
				$jobs [$x]->setType ( null );
195
				$jobs [$x]->setErrorDisplayed ( null );
196
				array_push ( $jobsNew, $jobs [$x] );
197
			}
198
			return $jobsNew;
199
		} 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...
200
			$this->handleException ( $e );
201
		}
202
	}
203
	
204
	/**
205
	 * TODO: Verify the security token and so on.
206
	 * The function the worker will call in order to set the jobs status.
207
	 * The worker should call it automatically after each processing step.
208
	 *
209
	 * @param integer $jobId        	
210
	 * @param boolean $failed        	
211
	 * @param string $errorMessage        	
212
	 */
213
	public function jobFinished($jobId, $failed, $errorMessage) {
214
		try {
215
			$job = $this->jobMapper->find ( $jobId );
216
			if (! $failed) {
217
				$job->setStatus ( 'PROCESSED' );
218
				$this->jobMapper->update ( $job );
219
			} else {
220
				$job->setStatus ( 'FAILED' );
221
				// TODO: add error message log and so on
222
				$this->jobMapper->update ( $job );
223
				$this->logger->error ( $errorMessage, [ 
224
						'app' => 'ocr' 
225
				] );
226
			}
227
		} 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...
228
			$this->handleException ( $e );
229
		}
230
	}
231
	
232
	/**
233
	 * Finishes all Processed files by copying them to the right path and deleteing the temp files.
234
	 * Returns the number of processed files.
235
	 * TODO: adjust behaviour for saving of the worker with the right file extension or not
236
	 *
237
	 * @return array
238
	 */
239
	public function handleProcessed() {
240
		try {
241
			$this->logger->debug ( 'Check if files were processed by ocr and if so, put them to the right dirs.', [ 
242
					'app' => 'ocr' 
243
			] );
244
			$processed = $this->jobMapper->findAllProcessed ( $this->userId );
245
			foreach ( $processed as $job ) {
246
				if ($job->getType () === 'tess' && $this->fileService->fileExists ( $job->getTempFile () . '.txt' )) {
247
					// Save the tmp file with newname
248
					$this->view->file_put_contents ( $job->getTarget (), $this->fileService->getFileContents ( $job->getTempFile () . '.txt' ) ); // need .txt because tesseract saves it like this
249
					                                                                                                                              // Cleaning temp files
250
					$this->jobMapper->delete ( $job );
251
					$this->fileService->execRemove ( $job->getTempFile () . '.txt' );
252
				} elseif ($job->getType () === 'mypdf' && $this->fileService->fileExists ( $job->getTempFile () )) {
253
					// Save the tmp file with newname
254
					$this->view->file_put_contents ( $job->getTarget (), $this->fileService->getFileContents ( $job->getTempFile () ) ); // don't need to extend with .pdf / it uses the tmp file to save
255
					$this->jobMapper->delete ( $job );
256
					$this->fileService->execRemove ( $job->getTempFile () );
257
				} else {
258
					$job->setStatus ( 'FAILED' );
259
					$this->jobMapper->update ( $job );
260
					throw new NotFoundException ( $this->l10n->t ( 'Temp file does not exist.' ) );
261
				}
262
			}
263
			return $processed;
264
		} 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...
265
			$this->handleException ( $e );
266
		}
267
	}
268
	
269
	/**
270
	 * Handles all failed orders of ocr processing queue and returns the job objects.
271
	 *
272
	 * @return array
273
	 */
274
	public function handleFailed() {
275
		try {
276
			$failed = $this->jobMapper->findAllFailed ( $this->userId );
277
			foreach ( $failed as $job ) {
278
				// clean the tempfile
279
				$this->fileService->execRemove ( $job->getTempFile () );
280
				// set error displayed
281
				$job->setErrorDisplayed ( true );
282
				$this->jobMapper->update ( $job );
283
			}
284
			$this->logger->debug ( 'Following jobs failed: ' . json_encode ( $failed ), [ 
285
					'app' => 'ocr' 
286
			] );
287
			return $failed;
288
		} 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...
289
			$this->handleException ( $e );
290
		}
291
	}
292
	
293
	/**
294
	 * Checks if the given languages are supported or not.
295
	 * TODO: $this->config->getAppValue()
296
	 *
297
	 * @param string[] $languages        	
298
	 * @return boolean
299
	 */
300
	private function checkForAcceptedLanguages($languages) {
301
		if (count ( array_diff ( $languages, [ 
302
				'deu',
303
				'eng' 
304
		] ) ) === 0) {
305
			return true;
306
		} else {
307
			return false;
308
		}
309
	}
310
	
311
	/**
312
	 * Checks if the process should be initiated without any language specified.
313
	 * 
314
	 * @param string[] $languages
315
	 * @return boolean
316
	 */
317
	private function noLanguage($languages) {
318
		if (in_array ( 'none', $languages )) {
319
			return true;
320
		} else {
321
			return false;
322
		}
323
	}
324
	
325
	/**
326
	 * Handle the possible thrown Exceptions from all methods of this class.
327
	 *
328
	 * @param Exception $e        	
329
	 * @throws Exception
330
	 * @throws NotFoundException
331
	 */
332 View Code Duplication
	private function handleException($e) {
333
		$this->logger->logException ( $e, [ 
334
				'app' => 'ocr',
335
				'message' => 'Exception during job service function processing' 
336
		] );
337
		if ($e instanceof NotFoundException) {
338
			throw new NotFoundException ( $e->getMessage () );
339
		} else {
340
			throw $e;
341
		}
342
	}
343
}