SessionController::save()   F
last analyzed

Complexity

Conditions 11
Paths 483

Size

Total Lines 90
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
dl 0
loc 90
ccs 0
cts 65
cp 0
rs 3.3109
c 0
b 0
f 0
cc 11
eloc 53
nc 483
nop 0
crap 132

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * ownCloud - Documents App
4
 *
5
 * @author Victor Dubiniuk
6
 * @copyright 2014 Victor Dubiniuk [email protected]
7
 *
8
 * This file is licensed under the Affero General Public License version 3 or
9
 * later.
10
 */
11
12
namespace OCA\Documents\Controller;
13
14
use \OCP\AppFramework\Controller;
15
use \OCP\IRequest;
16
use \OCP\AppFramework\Http;
17
use \OCP\AppFramework\Http\JSONResponse;
18
19
use \OCA\Documents\Db;
20
use \OCA\Documents\File;
21
use \OCA\Documents\Helper;
22
use OCA\Documents\Filter;
23
use \OC\Files\View;
24
25
class BadRequestException extends \Exception {
26
27
	protected $body = "";
28
29
	public function setBody($body){
30
		$this->body = $body;
31
	}
32
33
	public function getBody(){
34
		return $this->body;
35
	}
36
}
37
38
class SessionController extends Controller{
39
	
40
	protected $uid;
41
	protected $logger;
42
	protected $shareToken;
43
44
	public function __construct($appName, IRequest $request, $logger, $uid){
45
		parent::__construct($appName, $request);
46
		$this->uid = $uid;
47
		$this->logger = $logger;
48
	}
49
	
50
	/**
51
	 * @NoAdminRequired
52
	 * @PublicPage
53
	 */
54
	public function joinAsGuest($token, $name){
55
		$uid = substr($name, 0, 16);
56
		
57
		try {
58
			$file = File::getByShareToken($token);
59
			if ($file->isPasswordProtected() && !$file->checkPassword('')){
60
				throw new \Exception('Not authorized');
61
			}
62
63
			$response = array_merge( 
64
				Db\Session::start($uid, $file),
65
				[ 'status'=>'success' ]
66
			);
67
		} catch (\Exception $e){
68
			$this->logger->warning('Starting a session failed. Reason: ' . $e->getMessage(), ['app' => $this->appName]);
69
			$response = [ 'status'=>'error' ];
70
		}
71
		
72
		return $response;
73
	}
74
	
75
	/**
76
	 * @NoAdminRequired
77
	 * @PublicPage
78
	 */
79
	public function pollAsGuest($command, $args){
80
		$this->shareToken = $this->request->getParam('token');
81
		return $this->poll($command, $args);
82
	}
83
	
84
	/**
85
	 * Store the document content to its origin
86
	 * @NoAdminRequired
87
	 * @PublicPage
88
	 */
89
	public function saveAsGuest(){
90
		$this->shareToken = $this->request->getParam('token');
91
		return $this->save();
92
	}
93
	
94
	/**
95
	 * @NoAdminRequired
96
	 */
97
	public function join($fileId){
98
		try {
99
			$view = \OC\Files\Filesystem::getView();
100
			$path = $view->getPath($fileId);
101
			
102
			if ($view->isUpdatable($path)) {
103
				$file = new File($fileId);
104
				$response = Db\Session::start($this->uid, $file);
105
			} else {
106
				$info = $view->getFileInfo($path);
107
				$response = [
108
					'permissions' => $info['permissions'],
109
					'id' => $fileId
110
				];
111
			}
112
			$response = array_merge( 
113
					$response,
114
					[ 'status'=>'success' ]
115
			);
116
		} catch (\Exception $e){
117
			$this->logger->warning('Starting a session failed. Reason: ' . $e->getMessage(), [ 'app' => $this->appName ]);
118
			$response = [ 'status'=>'error' ];
119
		}
120
		
121
		return $response;
122
	}
123
	
124
	/**
125
	 * @NoAdminRequired
126
	 */
127
	public function poll($command, $args){
128
		$response = new JSONResponse();
129
130
		try{
131
			$esId = isset($args['es_id']) ? $args['es_id'] : null;
132
			$session = $this->loadSession($esId);
133
	
134
			$memberId = isset($args['member_id']) ? $args['member_id'] : null;
135
			$member = $this->loadMember($memberId);
136
137
			$this->validateSession($session);
138
	
139
			switch ($command){
140
				case 'sync_ops':
141
					$seqHead = (string) isset($args['seq_head']) ? $args['seq_head'] : null;
142
					if (!is_null($seqHead)){
143
						$ops = isset($args['client_ops']) ? $args['client_ops'] : [];
144
						
145
						$op = new Db\Op();
146
						$currentHead = $op->getHeadSeq($esId);
147
				
148
						try {
149
							$member->updateActivity($memberId);
150
						} catch (\Exception $e){
151
							//Db error. Not critical
152
						}
153
						$response->setData(
154
								$session->syncOps($memberId, $currentHead, $seqHead, $ops)
155
						);
156
157
						$inactiveMembers = $member->updateByTimeout($esId);
158
						foreach ($inactiveMembers as $inactive){
159
							$op->removeCursor($esId, $inactive);
160
							$op->removeMember($esId, $inactive);
161
						}
162
					} else {
163
						// Error - no seq_head passed
164
						throw new BadRequestException();
165
					}
166
167
					break;
168
				default:
169
					$ex = new BadRequestException();
170
					$ex->setBody(
171
							implode(',', $this->request->getParams())
172
					);
173
					throw $ex;
174
			}
175
		} catch (BadRequestException $e){
176
			$response->setStatus(Http::STATUS_BAD_REQUEST);
177
			$response->setData(
178
					[ 'err' => 'bad request:[' . $e->getBody() . ']' ]
179
			);
180
		}
181
		return $response;
182
	}
183
	
184
	/**
185
	 * Store the document content to its origin
186
	 * @NoAdminRequired
187
	 */
188
	public function save(){
189
		$response = new JSONResponse();
190
		try {
191
			$esId = $this->request->server['HTTP_WEBODF_SESSION_ID'];
192
			$session = $this->loadSession($esId);
193
			
194
			$memberId = $this->request->server['HTTP_WEBODF_MEMBER_ID'];
195
			$currentMember = $this->loadMember($memberId, $esId);
196
			
197
			// Extra info for future usage
198
			// $sessionRevision = $this->request->server['HTTP_WEBODF_SESSION_REVISION'];
199
			
200
			//NB ouch! New document content is passed as an input stream content
201
			$stream = fopen('php://input','r');
202
			if (!$stream){
203
				throw new \Exception('New content missing');
204
			}
205
			$content = stream_get_contents($stream);
206
207
			try {
208
				if ($currentMember->getIsGuest()){
209
					$file = File::getByShareToken($currentMember->getToken());
210
				} else {
211
					$file = new File($session->getFileId());
212
				}
213
				
214
				$view = $file->getOwnerView(true);
215
				$path = $file->getPath(true);
216
			} catch (\Exception $e){
217
				//File was deleted or unshared. We need to save content as new file anyway
218
				//Sorry, but for guests it would be lost :(
219
				if ($this->uid){
220
					$view = new View('/' . $this->uid . '/files');
221
		
222
					$dir = \OC::$server->getConfig()->getUserValue($this->uid, 'documents', 'save_path', '');
223
					$path = Helper::getNewFileName($view, $dir . 'New Document.odt');
224
				} else {
225
					throw $e;
226
				}
227
			}
228
			
229
			$member = new Db\Member();
230
			$members = $member->getActiveCollection($esId);
231
			$memberIds = array_map(
232
				function($x){
233
					return ($x['member_id']);
234
				},
235
				$members
236
			);
237
			
238
			// Active users except current user
239
			$memberCount = count($memberIds) - 1;
240
			
241
			if ($view->file_exists($path)){
242
				$currentHash = $view->hash('sha1', $path, false);
243
				
244
				if (!Helper::isVersionsEnabled() && $currentHash !== $session->getGenesisHash()){
245
					// Original file was modified externally. Save to a new one
246
					$path = Helper::getNewFileName($view, $path, '-conflict');
247
				}
248
				
249
				$mimetype = $view->getMimeType($path);
250
			} else {
251
				$mimetype = Storage::MIMETYPE_LIBREOFFICE_WORDPROCESSOR;
252
			}
253
			
254
			$data = Filter::write($content, $mimetype);
255
			
256
			if ($view->file_put_contents($path, $data['content'])){
257
				// Not a last user
258
				if ($memberCount>0){
259
					// Update genesis hash to prevent conflicts
260
					$this->logger->debug('Update hash', [ 'app' => $this->appName ]);
261
					$session->updateGenesisHash($esId, sha1($data['content']));
262
				} else {
263
					// Last user. Kill session data
264
					Db\Session::cleanUp($esId);
265
				}
266
				
267
				$view->touch($path);
268
			}
269
			$response->setData(['status'=>'success']);
270
		} catch (\Exception $e){
271
			$response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR);
272
			$response->setData([]);
273
			$this->logger->warning('Saving failed. Reason:' . $e->getMessage(), [ 'app' => $this->appName ]);
274
		}
275
276
		return $response;
277
	}
278
	
279
	protected function validateSession($session){
280
		try {
281
			if (is_null($this->shareToken)) {
282
				new File($session->getFileId());
283
			} else {
284
				File::getByShareToken($this->shareToken);
285
			}
286
		} catch (\Exception $e){
287
			$this->logger->warning('Error. Session no longer exists. ' . $e->getMessage(), [ 'app' => $this->appName ]);
288
			$ex = new BadRequestException();
289
			$ex->setBody(
290
					implode(',', $this->request->getParams())
291
			);
292
			throw $ex;
293
		}
294
	}
295
	
296
	protected function loadSession($esId){
297
		if (!$esId){
298
			throw new \Exception('Session id can not be empty');
299
		}
300
		
301
		$session = new Db\Session();
302
		$session->load($esId);
303
		if (!$session->getEsId()){
304
			throw new \Exception('Session does not exist');
305
		}
306
		return $session;
307
	}
308
	
309
	protected function loadMember($memberId, $expectedEsId = null){
310
		if (!$memberId){
311
			throw new \Exception('Member id can not be empty');
312
		}
313
		$member = new Db\Member();
314
		$member->load($memberId);
315
		//check if member belongs to the session
316
		if (!is_null($expectedEsId) && $expectedEsId !== $member->getEsId()){
317
			throw new \Exception($memberId . ' does not belong to session ' . $expectedEsId);
318
		}
319
		return $member;
320
	}
321
}
322