Completed
Push — update/full-sync-status ( 62ac53 )
by
unknown
10:48
created

Jetpack_Sync_Module_Full_Sync::start()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 48
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 21
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 48
rs 8.551
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 = 'jetpack_full_sync_status';
19
	const FULL_SYNC_TIMEOUT = 3600;
20
	private $send_modules = array();
21
22
	public function name() {
23
		return 'full-sync';
24
	}
25
26
	function init_listeners( $callable ) {
27
		// synthetic actions for full sync
28
		add_action( 'jetpack_full_sync_start', $callable );
29
		add_action( 'jetpack_full_sync_end', $callable );
30
	}
31
32
	function init_before_send() {
33
		// this is triggered after actions have been processed on the server
34
		add_action( 'jetpack_sync_processed_actions', array( $this, 'update_sent_progress_action' ) );
35
	}
36
37
	function start( $modules = null ) {
38
		if ( ! $this->should_start_full_sync() ) {
39
			return false;
40
		}
41
42
		// ensure listener is loaded so we can guarantee full sync actions are enqueued
43
		require_once dirname( __FILE__ ) . '/class.jetpack-sync-listener.php';
44
		Jetpack_Sync_Listener::get_instance();
45
46
		/**
47
		 * Fires when a full sync begins. This action is serialized
48
		 * and sent to the server so that it knows a full sync is coming.
49
		 *
50
		 * @since 4.2.0
51
		 */
52
		do_action( 'jetpack_full_sync_start' );
53
		$this->set_status_queuing_started();
54
55
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
56
			$module_name = $module->name();
57
			if ( is_array( $modules ) && ! in_array( $module_name, $modules ) ) {
58
				continue;
59
			}
60
			$this->send_modules[] = $module_name;
61
62
			$items_enqueued = $module->enqueue_full_sync_actions();
63
			if ( 0 !== $items_enqueued ) {
64
				$status = $this->get_status( 'queue', 0, $module_name );
65
				$status += $items_enqueued;
66
				$this->update_status( 'queue', $status, $module_name );
67
			}
68
		}
69
70
		$this->set_status_queuing_finished();
71
72
		$store = new Jetpack_Sync_WP_Replicastore();
73
74
		/**
75
		 * Fires when a full sync ends. This action is serialized
76
		 * and sent to the server with checksums so that we can confirm the
77
		 * sync was successful.
78
		 *
79
		 * @since 4.2.0
80
		 */
81
		do_action( 'jetpack_full_sync_end', $store->checksum_all() );
82
83
		return true;
84
	}
85
86
	private function should_start_full_sync() {
87
		$status_started = $this->get_status( 'started', null );
88
		$status_finished = $this->get_status( 'finished', null );
89
90
		// We should try sync if we haven't started it yet or if we have finished it.
91
		if ( is_null( $status_started ) || is_integer( $status_finished ) ) {
92
			return true;
93
		}
94
95
		// allow enqueuing if last full sync was started more than FULL_SYNC_TIMEOUT seconds ago
96
		if ( intval( $status_started ) + self::FULL_SYNC_TIMEOUT < time() ) {
97
			return true;
98
		}
99
100
		return false;
101
	}
102
103
	function update_sent_progress_action( $actions ) {
104
		// quick way to map to first items with an array of arrays
105
		$actions_with_counts = array_count_values( array_map( 'reset', $actions ) );
106
		$status_started = $this->get_status( 'started', null );
107
		$status_finished = $this->get_status( 'finished', null );
108
		if ( is_null( $status_started ) || $status_finished ) {
109
			return;
110
		}
111
112
		if ( isset( $actions_with_counts['jetpack_full_sync_start'] ) ) {
113
			$this->update_status( 'sent_started', time() );
114
		}
115
116
		$status_sent = array();
117
118
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
119
			$module_name    = $module->name();
120
			$module_actions = $module->get_full_sync_actions();
121
			foreach ( $module_actions as $module_action ) {
122
				
123
				if ( isset( $actions_with_counts[ $module_action ] ) ) {
124
					if ( ! isset( $status_sent[ $module_name ] ) ) {
125
						$status_sent[ $module_name ] = $this->get_status( 'sent', 0, $module_name );
126
					}
127
					$status_sent[ $module_name ] += $actions_with_counts[ $module_action ];
128
				}
129
			}
130
131
		}
132
133
		foreach( $status_sent as $sent_module_name => $sent_module_value ) {
134
			$this->update_status( 'sent', $sent_module_value, $sent_module_name );
135
		}
136
137
		if ( isset( $actions_with_counts['jetpack_full_sync_end'] ) ) {
138
			$this->update_status( 'finished', time() );
139
		}
140
141
142
	}
143
144
	private function set_status_queuing_started() {
145
		$this->clear_status();
146
		$this->update_status( 'started', time() );
147
	}
148
149
	private function set_status_queuing_finished() {
150
		$this->update_status( 'queue_finished', time() );
151
	}
152
153
	private $initial_status = array(
154
		'started'        => null,
155
		'queue_finished' => null,
156
		'sent_started'   => null,
157
		'finished'       => null,
158
		'sent'           => array(),
159
		'queue'          => array(),
160
	);
161
162
	public function get_status( $status_key, $status_default_value, $module_name = null ) {
163
		if ( $module_name ) {
164
			return get_option( self::STATUS_OPTION . '_' . $status_key . '_' . $module_name, $status_default_value );
165
		}
166
		return get_option( self::STATUS_OPTION . '_' . $status_key, $status_default_value );
167
	}
168
169
	public function update_status( $status_key, $status_value, $module_name = null ) {
170
		if ( $module_name ) {
171
			return $this->update_option( self::STATUS_OPTION . '_' . $status_key . '_' . $module_name, $status_value );
172
		}
173
		return $this->update_option( self::STATUS_OPTION . '_' . $status_key, $status_value );
174
	}
175
176
	private function update_option( $option_name, $new_value ) {
177
		global $wp_version;
178
		if ( version_compare( '4.2.0', $wp_version, '<=' ) ) {
179
			return update_option( $option_name, $new_value, false );
180
		}
181
		if ( get_option( $option_name ) !== false ) {
182
			return update_option( $option_name, $new_value );
183
		}
184
		return add_option( $option_name, $new_value, null, false );
185
	}
186
187
	public function get_full_status() {
188
		$status = array();
189
		$module_names = array();
190
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
191
			$module_names[] = $module->name();
192
		}
193
		
194
		foreach( $this->initial_status as $status_key => $initial_value ) {
195
			if ( is_array( $initial_value ) ) {
196
				$status[ $status_key ] = array();
197
				foreach( $module_names as $module_name ) {
198
					$module_status = $this->get_status( $status_key, null, $module_name );
199
					if ( null !== $module_status ) {
200
						$status[ $status_key ][ $module_name ] = (int) $module_status;
201
					}
202
				}
203
			} else {
204
				$status[ $status_key ] = $this->get_status( $status_key, null );
205
			}
206
		}
207
		return $status;
208
	}
209
210
211
	public function clear_status() {
212
		$module_names = array();
213
		foreach ( Jetpack_Sync_Modules::get_modules() as $module ) {
214
			$module_names[] = $module->name();
215
		}
216
217
		foreach( $this->initial_status as $status_key => $initial_value ) {
218
			if ( is_array( $initial_value ) ) {
219
				foreach( $module_names as $module_name ) {
220
					delete_option( self::STATUS_OPTION . '_' . $status_key . '_' . $module_name );
221
				}
222
			} else {
223
				delete_option( self::STATUS_OPTION . '_' . $status_key );
224
			}
225
		}
226
227
	}
228
}
229