Completed
Push — update/sync-status-endpoint-ad... ( 2994d8...5ec62d )
by
unknown
32:37 queued 21:41
created

Jetpack_Sync_Module_Full_Sync   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 280
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 280
rs 7.9487
c 0
b 0
f 0
wmc 52
lcom 1
cbo 5

17 Methods

Rating   Name   Duplication   Size   Complexity  
A name() 0 3 1
A init_full_sync_listeners() 0 6 1
A init_before_send() 0 4 1
D update_sent_progress_action() 0 33 9
F start() 0 96 16
A get_action_name() 0 6 3
A is_started() 0 3 1
A is_finished() 0 3 1
B get_status() 0 34 6
A clear_status() 0 14 2
A reset_data() 0 6 1
A get_status_option() 0 12 3
A update_status_option() 0 4 1
A enable_queue_rate_limit() 0 8 1
A disable_queue_rate_limit() 0 4 1
A queue_item_added() 0 3 1
A queue_items_added() 0 16 3

How to fix   Complexity   

Complex Class

Complex classes like Jetpack_Sync_Module_Full_Sync 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Jetpack_Sync_Module_Full_Sync, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This class does a full resync of the database by
5
 * enqueuing an outbound action for every single object
6
 * that we care about.
7
 *
8
 * This class, and its related class Jetpack_Sync_Module, contain a few non-obvious optimisations that should be explained:
9
 * - we fire an action called jetpack_full_sync_start so that WPCOM can erase the contents of the cached database
10
 * - for each object type, we page through the object IDs and enqueue them by firing some monitored actions
11
 * - we load the full objects for those IDs in chunks of Jetpack_Sync_Module::ARRAY_CHUNK_SIZE (to reduce the number of MySQL calls)
12
 * - we fire a trigger for the entire array which the Jetpack_Sync_Listener then serializes and queues.
13
 */
14
15
require_once 'class.jetpack-sync-wp-replicastore.php';
16
17
class Jetpack_Sync_Module_Full_Sync extends Jetpack_Sync_Module {
18
	const STATUS_OPTION_PREFIX = 'jetpack_sync_full_';
19
	const FULL_SYNC_TIMEOUT = 3600;
20
21
	private $items_added_since_last_pause;
22
	private $last_pause_time;
23
	private $queue_rate_limit;
24
25
	public function name() {
26
		return 'full-sync';
27
	}
28
29
	function init_full_sync_listeners( $callable ) {
30
		// synthetic actions for full sync
31
		add_action( 'jetpack_full_sync_start', $callable );
32
		add_action( 'jetpack_full_sync_end', $callable );
33
		add_action( 'jetpack_full_sync_cancelled', $callable );
34
	}
35
36
	function init_before_send() {
37
		// this is triggered after actions have been processed on the server
38
		add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
39
	}
40
41
	function start( $modules = null ) {
42
		$was_already_running = $this->is_started() && ! $this->is_finished();
43
44
		// remove all evidence of previous full sync items and status
45
		$this->reset_data();
46
47
		$this->enable_queue_rate_limit();
48
49
		if ( $was_already_running ) {
50
			/**
51
			 * Fires when a full sync is cancelled.
52
			 *
53
			 * @since 4.2.0
54
			 */
55
			do_action( 'jetpack_full_sync_cancelled' );
56
		}
57
58
		/**
59
		 * Fires when a full sync begins. This action is serialized
60
		 * and sent to the server so that it knows a full sync is coming.
61
		 *
62
		 * @since 4.2.0
63
		 */
64
		do_action( 'jetpack_full_sync_start', $modules );
65
		$this->update_status_option( 'started', time() );
66
67
		// configure modules
68
		if ( ! is_array( $modules ) ) {
69
			$modules = array();
70
		}
71
72
		if ( isset( $modules['users'] ) && 'initial' === $modules['users'] ) {
73
			$user_module = Jetpack_Sync_Modules::get_module( 'users' );
74
			$modules['users'] = $user_module->get_initial_sync_user_config();
75
		}
76
77
		// by default, all modules are fully enabled
78
		if ( count( $modules ) === 0 ) {
79
			$default_module_config = true;
80
		} else {
81
			$default_module_config = false;
82
		}
83
84
		// set default configuration, calculate totals, and save configuration if totals > 0
85
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
86
			$module_name = $module->name();
87
			if ( ! isset( $modules[ $module_name ] ) ) {
88
				$modules[ $module_name ] = $default_module_config;
89
			}
90
91
			// check if this module is enabled
92
			if ( ! ( $module_config = $modules[ $module_name ] ) ) {
93
				continue;
94
			}
95
96
			$total_items = $module->estimate_full_sync_actions( $module_config );
97
98
			if ( ! is_null( $total_items ) && $total_items > 0 ) {
99
				$this->update_status_option( "{$module_name}_total", $total_items );
100
				$this->update_status_option( "{$module_name}_config", $module_config );
101
			}
102
		}
103
104
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
105
			$module_name   = $module->name();
106
			$module_config = $modules[ $module_name ];
107
108
			// check if this module is enabled
109
			if ( ! $module_config ) {
110
				continue;
111
			}
112
113
			$items_enqueued = $module->enqueue_full_sync_actions( $module_config );
114
115
			if ( ! is_null( $items_enqueued ) && $items_enqueued > 0 ) {
116
				$this->update_status_option( "{$module_name}_queued", $items_enqueued );
117
			}
118
		}
119
120
		$this->update_status_option( 'queue_finished', time() );
121
122
		$store = new Jetpack_Sync_WP_Replicastore();
123
124
		/**
125
		 * Fires when a full sync ends. This action is serialized
126
		 * and sent to the server with checksums so that we can confirm the
127
		 * sync was successful.
128
		 *
129
		 * @since 4.2.0
130
		 */
131
		do_action( 'jetpack_full_sync_end', $store->checksum_all() );
132
133
		$this->disable_queue_rate_limit();
134
135
		return true;
136
	}
137
138
	function update_sent_progress_action( $actions ) {
139
140
		// quick way to map to first items with an array of arrays
141
		$actions_with_counts = array_count_values( array_filter( array_map( array( $this, 'get_action_name' ), $actions ) ) );
142
143
		if ( ! $this->is_started() || $this->is_finished() ) {
144
			return;
145
		}
146
147
		if ( isset( $actions_with_counts['jetpack_full_sync_start'] ) ) {
148
			$this->update_status_option( 'sent_started', time() );
149
		}
150
151
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
152
			$module_actions     = $module->get_full_sync_actions();
153
			$status_option_name = "{$module->name()}_sent";
154
			$items_sent         = $this->get_status_option( $status_option_name, 0 );
155
156
			foreach ( $module_actions as $module_action ) {
157
				if ( isset( $actions_with_counts[ $module_action ] ) ) {
158
					$items_sent += $actions_with_counts[ $module_action ];
159
				}
160
			}
161
162
			if ( $items_sent > 0 ) {
163
				$this->update_status_option( $status_option_name, $items_sent );
164
			}	
165
		}
166
167
		if ( isset( $actions_with_counts['jetpack_full_sync_end'] ) ) {
168
			$this->update_status_option( 'finished', time() );
169
		}
170
	}
171
172
	public function get_action_name( $queue_item ) {
173
		if ( is_array( $queue_item ) && isset( $queue_item[0] ) ) {
174
			return $queue_item[0];
175
		}
176
		return false;
177
	}
178
179
	public function is_started() {
180
		return !! $this->get_status_option( 'started' );
181
	}
182
183
	public function is_finished() {
184
		return !! $this->get_status_option( 'finished' );
185
	}
186
187
	public function get_status() {
188
		$status = array(
189
			'started'        => $this->get_status_option( 'started' ),
190
			'queue_finished' => $this->get_status_option( 'queue_finished' ),
191
			'sent_started'   => $this->get_status_option( 'sent_started' ),
192
			'finished'       => $this->get_status_option( 'finished' ),
193
			'sent'           => array(),
194
			'queue'          => array(),
195
			'config'         => array(),
196
			'total'          => array(),
197
		);
198
199
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
200
			$name = $module->name();
201
202
			if ( $total = $this->get_status_option( "{$name}_total" ) ) {
203
				$status[ 'total' ][ $name ] = $total;
204
			}
205
206
			if ( $queued = $this->get_status_option( "{$name}_queued" ) ) {
207
				$status[ 'queue' ][ $name ] = $queued;
208
			}
209
			
210
			if ( $sent = $this->get_status_option( "{$name}_sent" ) ) {
211
				$status[ 'sent' ][ $name ] = $sent;
212
			}
213
214
			if ( $config = $this->get_status_option( "{$name}_config" ) ) {
215
				$status[ 'config' ][ $name ] = $config;
216
			}
217
		}
218
219
		return $status;
220
	}
221
222
	public function clear_status() {
223
		$prefix = self::STATUS_OPTION_PREFIX;
224
		delete_option( "{$prefix}_started" );
225
		delete_option( "{$prefix}_queue_finished" );
226
		delete_option( "{$prefix}_sent_started" );
227
		delete_option( "{$prefix}_finished" );
228
229
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
230
			delete_option( "{$prefix}_{$module->name()}_total" );
231
			delete_option( "{$prefix}_{$module->name()}_queued" );
232
			delete_option( "{$prefix}_{$module->name()}_sent" );
233
			delete_option( "{$prefix}_{$module->name()}_config" );
234
		}
235
	}
236
237
	public function reset_data() {
238
		$this->clear_status();
239
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-listener.php';
240
		$listener = Jetpack_Sync_Listener::get_instance();
241
		$listener->get_full_sync_queue()->reset();
242
	}
243
244
	private function get_status_option( $option, $default = null ) {
245
		$prefix = self::STATUS_OPTION_PREFIX;
246
247
		$value = get_option( "{$prefix}_{$option}", $default );
248
		
249
		if ( ! $value ) {
250
			// don't cast to int if we didn't find a value - we want to preserve null or false as sentinals
251
			return $default;
252
		}
253
254
		return is_numeric( $value ) ? intval( $value ) : $value;
255
	}
256
257
	private function update_status_option( $name, $value ) {
258
		$prefix = self::STATUS_OPTION_PREFIX;
259
		update_option( "{$prefix}_{$name}", $value, false );
260
	}
261
262
	private function enable_queue_rate_limit() {
263
		$this->queue_rate_limit = Jetpack_Sync_Settings::get_setting( 'queue_max_writes_sec' );
264
		$this->items_added_since_last_pause = 0;
265
		$this->last_pause_time = microtime( true );
266
267
		add_action( 'jpsq_item_added', array( $this, 'queue_item_added' ) );
268
		add_action( 'jpsq_items_added', array( $this, 'queue_items_added' ) );
269
	}
270
271
	private function disable_queue_rate_limit() {
272
		remove_action( 'jpsq_item_added', array( $this, 'queue_item_added' ) );
273
		remove_action( 'jpsq_items_added', array( $this, 'queue_items_added' ) );
274
	}
275
276
	public function queue_item_added() {
277
		$this->queue_items_added( 1 );
278
	}
279
280
	public function queue_items_added( $item_count ) {
0 ignored issues
show
Unused Code introduced by
The parameter $item_count is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
281
		// jpsq_item_added and jpsq_items_added both exec 1 db query, 
282
		// so we ignore $item_count and treat it as always 1
283
		$this->items_added_since_last_pause += 1; 
284
285
		if ( $this->items_added_since_last_pause > $this->queue_rate_limit ) {
286
			// sleep for the rest of the second
287
			$sleep_til = $this->last_pause_time + 1.0;
288
			$sleep_duration = $sleep_til - microtime( true );
289
			if ( $sleep_duration > 0.0 ) {
290
				usleep( $sleep_duration * 1000000 );
291
				$this->last_pause_time = microtime( true );
292
			}
293
			$this->items_added_since_last_pause = 0;
294
		}
295
	}
296
}
297