FolderSync   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 129
dl 0
loc 253
rs 8.8798
c 0
b 0
f 0
wmc 44

1 Method

Rating   Name   Duplication   Size   Complexity  
F Handle() 0 245 44

How to fix   Complexity   

Complex Class

Complex classes like FolderSync often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FolderSync, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
6
 *
7
 * Provides the FOLDERSYNC command
8
 */
9
10
class FolderSync extends RequestProcessor {
11
	/**
12
	 * Handles the FolderSync command.
13
	 *
14
	 * @param int $commandCode
15
	 *
16
	 * @return bool
17
	 */
18
	public function Handle($commandCode) {
19
		// Parse input
20
		if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) {
21
			return false;
22
		}
23
24
		if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) {
25
			return false;
26
		}
27
28
		$synckey = self::$decoder->getElementContent();
29
30
		if (!self::$decoder->getElementEndTag()) {
31
			return false;
32
		}
33
34
		// every FolderSync with SyncKey 0 should return the supported AS version & command headers
35
		if ($synckey == "0") {
36
			self::$specialHeaders = [];
37
			self::$specialHeaders[] = GSync::GetSupportedProtocolVersions();
38
			self::$specialHeaders[] = GSync::GetSupportedCommands();
39
		}
40
41
		$status = SYNC_FSSTATUS_SUCCESS;
42
		$newsynckey = $synckey;
43
44
		try {
45
			$syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey);
46
47
			// We will be saving the sync state under 'newsynckey'
48
			$newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey);
49
50
			// there are no SyncParameters for the hierarchy, but we use it to save the latest synckeys
51
			$spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState(false);
52
		}
53
		catch (StateNotFoundException $snfex) {
54
			$status = SYNC_FSSTATUS_SYNCKEYERROR;
55
		}
56
		catch (StateInvalidException $sive) {
57
			$status = SYNC_FSSTATUS_SYNCKEYERROR;
58
		}
59
60
		// The ChangesWrapper caches all imports in-memory, so we can send a change count
61
		// before sending the actual data.
62
		// the HierarchyCache is notified and the changes from the PIM are transmitted to the actual backend
63
		$changesMem = self::$deviceManager->GetHierarchyChangesWrapper();
64
65
		// the hierarchyCache should now fully be initialized - check for changes in the additional folders
66
		$changesMem->Config(GSync::GetAdditionalSyncFolders(false), ChangesMemoryWrapper::SYNCHRONIZING);
67
68
		// reset to default store in backend
69
		self::$backend->Setup(false);
70
71
		// process incoming changes
72
		if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) {
73
			// Ignore <Count> if present
74
			if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) {
75
				self::$decoder->getElementContent();
76
				if (!self::$decoder->getElementEndTag()) {
77
					return false;
78
				}
79
			}
80
81
			// Process the changes (either <Add>, <Modify>, or <Remove>)
82
			$element = self::$decoder->getElement();
83
84
			if ($element[EN_TYPE] != EN_TYPE_STARTTAG) {
85
				return false;
86
			}
87
88
			$importer = false;
89
			WBXMLDecoder::ResetInWhile("folderSyncIncomingChange");
90
			while (WBXMLDecoder::InWhile("folderSyncIncomingChange")) {
91
				$folder = new SyncFolder();
92
				if (!$folder->Decode(self::$decoder)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $folder->Decode(self::decoder) of type false|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
93
					break;
94
				}
95
96
				// add the backendId to the SyncFolder object
97
				$folder->BackendId = self::$deviceManager->GetBackendIdForFolderId($folder->serverid);
98
99
				try {
100
					if ($status == SYNC_FSSTATUS_SUCCESS && !$importer) {
101
						// Configure the backends importer with last state
102
						$importer = self::$backend->GetImporter();
103
						$importer->Config($syncstate);
104
						// the messages from the PIM will be forwarded to the backend
105
						$changesMem->forwardImporter($importer);
106
					}
107
108
					if ($status == SYNC_FSSTATUS_SUCCESS) {
109
						switch ($element[EN_TAG]) {
110
							case SYNC_ADD:
111
							case SYNC_MODIFY:
112
								$serverid = $changesMem->ImportFolderChange($folder);
0 ignored issues
show
Unused Code introduced by
The assignment to $serverid is dead and can be removed.
Loading history...
113
								break;
114
115
							case SYNC_REMOVE:
116
								$serverid = $changesMem->ImportFolderDeletion($folder);
117
								break;
118
						}
119
					}
120
					else {
121
						SLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname));
122
						self::$topCollector->AnnounceInformation("Incoming change ignored", true);
123
					}
124
				}
125
				catch (StatusException $stex) {
126
					$status = $stex->getCode();
127
				}
128
			}
129
130
			if (!self::$decoder->getElementEndTag()) {
131
				return false;
132
			}
133
		}
134
		// no incoming changes
135
		else {
136
			// check for a potential process loop
137
			if ($synckey != "0" && self::$deviceManager->IsHierarchyFullResyncRequired()) {
138
				$status = SYNC_FSSTATUS_SYNCKEYERROR;
139
				self::$deviceManager->AnnounceProcessStatus(false, $status);
140
			}
141
		}
142
143
		if (!self::$decoder->getElementEndTag()) {
144
			return false;
145
		}
146
147
		// We have processed incoming foldersync requests, now send the PIM
148
		// our changes
149
150
		// Output our WBXML reply now
151
		self::$encoder->StartWBXML();
152
153
		self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC);
154
155
		if ($status == SYNC_FSSTATUS_SUCCESS) {
156
			try {
157
				// do nothing if this is an invalid device id (like the 'validate' Androids internal client sends)
158
				if (!Request::IsValidDeviceID()) {
159
					throw new StatusException(sprintf("Request::IsValidDeviceID() indicated that '%s' is not a valid device id", Request::GetDeviceID()), SYNC_FSSTATUS_SERVERERROR);
160
				}
161
162
				// Changes from backend are sent to the MemImporter and processed for the HierarchyCache.
163
				// The state which is saved is from the backend, as the MemImporter is only a proxy.
164
				$exporter = self::$backend->GetExporter();
165
166
				$exporter->Config($syncstate);
167
				$exporter->InitializeExporter($changesMem);
168
169
				// Stream all changes to the ImportExportChangesMem
170
				$totalChanges = $exporter->GetChangeCount();
171
				$exported = 0;
172
				$partial = false;
173
				while (is_array($exporter->Synchronize())) {
174
					++$exported;
175
176
					if (time() % 4) {
177
						self::$topCollector->AnnounceInformation(sprintf("Exported %d from %d folders", $exported, $totalChanges));
178
					}
179
180
					// if partial sync is allowed, stop if this takes too long
181
					if (USE_PARTIAL_FOLDERSYNC && Request::IsRequestTimeoutReached()) {
182
						SLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): Exporting folders is too slow. In %d seconds only %d from %d changes were processed.", time() - $_SERVER["REQUEST_TIME"], $exported, $totalChanges));
183
						self::$topCollector->AnnounceInformation(sprintf("Partial export of %d out of %d folders", $exported, $totalChanges), true);
184
						self::$deviceManager->SetFolderSyncComplete(false);
185
						$partial = true;
186
187
						break;
188
					}
189
				}
190
191
				// update the foldersync complete flag
192
				if (USE_PARTIAL_FOLDERSYNC && $partial === false && self::$deviceManager->GetFolderSyncComplete() === false) {
193
					// say that we are done with partial syncing
194
					self::$deviceManager->SetFolderSyncComplete(true);
195
					// reset the loop data to prevent any loop detection to kick in now
196
					self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUserString(), Request::GetDeviceID());
197
					SLog::Write(LOGLEVEL_INFO, "Request->HandleFolderSync(): Chunked exporting of folders completed successfully");
198
				}
199
200
				// get the new state from the backend
201
				$newsyncstate = (isset($exporter)) ? $exporter->GetState() : "";
202
			}
203
			catch (StatusException $stex) {
204
				if ($stex->getCode() == SYNC_FSSTATUS_CODEUNKNOWN) {
205
					$status = SYNC_FSSTATUS_SYNCKEYERROR;
206
				}
207
				else {
208
					$status = $stex->getCode();
209
				}
210
			}
211
		}
212
213
		self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
214
		self::$encoder->content($status);
215
		self::$encoder->endTag();
216
217
		if ($status == SYNC_FSSTATUS_SUCCESS) {
218
			self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
219
			$synckey = ($changesMem->IsStateChanged()) ? $newsynckey : $synckey;
220
			self::$encoder->content($synckey);
221
			self::$encoder->endTag();
222
223
			// Stream folders directly to the PDA
224
			$streamimporter = new ImportChangesStream(self::$encoder, false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type SyncObject expected by parameter $class of ImportChangesStream::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

224
			$streamimporter = new ImportChangesStream(self::$encoder, /** @scrutinizer ignore-type */ false);
Loading history...
225
			$changesMem->InitializeExporter($streamimporter);
226
			$changeCount = $changesMem->GetChangeCount();
227
228
			self::$encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES);
229
230
			self::$encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT);
231
			self::$encoder->content($changeCount);
232
			self::$encoder->endTag();
233
			while ($changesMem->Synchronize());
234
235
			self::$encoder->endTag();
236
			self::$topCollector->AnnounceInformation(sprintf("Outgoing %d folders", $changeCount), true);
237
238
			if ($changeCount == 0) {
239
				self::$deviceManager->CheckFolderData();
240
			}
241
			// everything fine, save the sync state for the next time
242
			if ($synckey == $newsynckey) {
243
				self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $newsyncstate);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $newsyncstate does not seem to be defined for all execution paths leading up to this point.
Loading history...
244
245
				// update SPA & save it
246
				$spa->SetSyncKey($newsynckey);
247
				$spa->SetFolderId(false);
248
249
				// invalidate all pingable flags
250
				SyncCollections::InvalidatePingableFlags();
251
			}
252
			// save the SyncParameters if it changed or the reference policy key is not set or different
253
			if ($spa->IsDataChanged() || !$spa->HasReferencePolicyKey() || GSync::GetProvisioningManager()->ProvisioningRequired($spa->GetReferencePolicyKey(), true, false)) {
254
				// saves the SPA (while updating the reference policy key)
255
				$spa->SetLastSynctime(time());
256
				self::$deviceManager->GetStateManager()->SetSynchedFolderState($spa);
257
			}
258
		}
259
260
		self::$encoder->endTag();
261
262
		return true;
263
	}
264
}
265