PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.11
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.11
1.1.11 1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 All 35 releases
← All changes | includes/desktop-files/heartbeat.php +83 -70 0.9.3 → 1.1.11 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Files Heartbeat sync (PHP).
3 + * OpenStation — Files Heartbeat sync (PHP).
4 4 *
5 5 * Piggybacks on the existing WordPress Heartbeat tick — the same
6 6 * channel `presence.php` uses — so connected clients see folder
7 7 * sharing changes and other users' placement edits inside one
@@ -6,13 +6,13 @@
6 6 * channel `presence.php` uses — so connected clients see folder
7 7 * sharing changes and other users' placement edits inside one
8 8 * cross-feature poll instead of N parallel ones.
9 9 *
10 - * Wire format. Client sends `desktop_mode_files_subscribe` keyed
10 + * Wire format. Client sends `openstation_files_subscribe` keyed
11 11 * to three version markers:
12 12 *
13 13 * {
14 - * desktop_mode_files_subscribe: {
14 + * openstation_files_subscribe: {
15 15 * folderVersions: { '<folderId>': lastSeenUpdatedAtMs, ... },
16 16 * placementsVersion: lastSeenUpdatedAtMs,
17 17 * sharesVersion: lastSeenInvitedAtMs
18 18 * }
@@ -19,9 +19,9 @@
19 19 * }
20 20 *
21 21 * Server responds with deltas + tombstones:
22 22 *
23 - * desktop_mode_files: {
23 + * openstation_files: {
24 24 * placements: [ <RestPlacementShape> ], // upserts
25 25 * folders: [ <RestFolderShape> ], // upserts (incl. share-mode flips)
26 26 * removed: {
27 27 * placements: [ ids ],
@@ -33,38 +33,35 @@
33 33 * serverTimeMs: int,
34 34 * truncated: bool
35 35 * }
36 36 *
37 - * Truncation kicks in when more than `desktop_mode_files_heartbeat_max_rows`
37 + * Truncation kicks in when more than `openstation_files_heartbeat_max_rows`
38 38 * (default 200) rows match — clients fall back to a full REST
39 39 * resync. The default cap is per-payload, not per-folder, so a
40 40 * massive shared folder doesn't starve other folders' deltas.
41 41 *
42 - * @package WPDesktopMode
43 - * @since 0.9.0
42 + * @package OpenStation
44 43 */
45 44
46 45 defined( 'ABSPATH' ) || exit;
47 46
48 47 /**
49 - * @since 0.9.0
50 - *
51 48 * @param array $response Pre-filtered response.
52 49 * @param array $data Client-sent payload.
53 50 * @return array
54 51 */
55 -function desktop_mode_files_heartbeat_received( $response, $data ) {
52 +function openstation_files_heartbeat_received( $response, $data ) {
56 53 if ( ! is_array( $response ) ) {
57 54 $response = array();
58 55 }
59 - if ( empty( $data['desktop_mode_files_subscribe'] ) || ! is_array( $data['desktop_mode_files_subscribe'] ) ) {
56 + if ( empty( $data['openstation_files_subscribe'] ) || ! is_array( $data['openstation_files_subscribe'] ) ) {
60 57 return $response;
61 58 }
62 - if ( ! function_exists( 'desktop_mode_is_enabled' ) || ! desktop_mode_is_enabled() ) {
59 + if ( ! function_exists( 'openstation_is_enabled' ) || ! openstation_is_enabled() ) {
63 60 return $response;
64 61 }
65 62
66 - $sub = $data['desktop_mode_files_subscribe'];
63 + $sub = $data['openstation_files_subscribe'];
67 64 $folder_v = isset( $sub['folderVersions'] ) && is_array( $sub['folderVersions'] )
68 65 ? $sub['folderVersions']
69 66 : array();
70 67 $plc_v = isset( $sub['placementsVersion'] ) ? (int) $sub['placementsVersion'] : 0;
@@ -79,15 +76,13 @@
79 76 * Filter the per-payload row cap. Lower this on slow links
80 77 * to force REST fallback sooner; raise it for fast-LAN
81 78 * intranets where a fatter Heartbeat is fine.
82 79 *
83 - * @since 0.9.0
84 - *
85 80 * @param int $cap Default 200.
86 81 */
87 - $cap = max( 1, (int) apply_filters( 'desktop_mode_files_heartbeat_max_rows', 200 ) );
82 + $cap = max( 1, (int) apply_filters( 'openstation_files_heartbeat_max_rows', 200 ) );
88 83
89 - $response['desktop_mode_files'] = desktop_mode_files_compute_heartbeat_delta(
84 + $response['openstation_files'] = openstation_files_compute_heartbeat_delta(
90 85 $user_id,
91 86 $folder_v,
92 87 $plc_v,
93 88 $cap,
@@ -94,15 +89,13 @@
94 89 $shr_v
95 90 );
96 91 return $response;
97 92 }
98 -add_filter( 'heartbeat_received', 'desktop_mode_files_heartbeat_received', 5, 2 );
93 +add_filter( 'heartbeat_received', 'openstation_files_heartbeat_received', 5, 2 );
99 94
100 95 /**
101 96 * Compute the delta payload for a viewer.
102 97 *
103 - * @since 0.9.0
104 - *
105 98 * @param int $user_id Viewer.
106 99 * @param array $folder_versions `{ folderId => lastSeenUpdatedAtMs }`.
107 100 * @param int $placements_version Last-seen `updated_at_ms` for placements.
108 101 * @param int $cap Row cap.
@@ -112,19 +105,19 @@
112 105 * to invites the client hasn't seen
113 106 * yet. Defaults to `0` (deliver all).
114 107 * @return array
115 108 */
116 -function desktop_mode_files_compute_heartbeat_delta( $user_id, $folder_versions, $placements_version, $cap, $shares_version = 0 ) {
109 +function openstation_files_compute_heartbeat_delta( $user_id, $folder_versions, $placements_version, $cap, $shares_version = 0 ) {
117 110 global $wpdb;
118 111
119 - $tables = desktop_mode_files_table_names();
112 + $tables = openstation_files_table_names();
120 113 $truncated = false;
121 114
122 115 // 1) Visible folders the viewer should know about. We send
123 - // the FULL row when its `updated_at_ms` exceeds whatever
124 - // the client last saw (or the client doesn't know about
125 - // it at all).
126 - $visible = desktop_mode_files_get_visible_folders( $user_id );
116 + // the FULL row when its `updated_at_ms` exceeds whatever
117 + // the client last saw (or the client doesn't know about
118 + // it at all).
119 + $visible = openstation_files_get_visible_folders( $user_id );
127 120 $folder_upserts = array();
128 121 foreach ( $visible as $row ) {
129 122 $id = (int) $row['id'];
130 123 $client_ts = isset( $folder_versions[ (string) $id ] )
@@ -130,9 +123,9 @@
130 123 $client_ts = isset( $folder_versions[ (string) $id ] )
131 124 ? (int) $folder_versions[ (string) $id ]
132 125 : 0;
133 126 if ( (int) $row['updated_at_ms'] > $client_ts ) {
134 - $folder_upserts[] = desktop_mode_files_shape_folder( $row );
127 + $folder_upserts[] = openstation_files_shape_folder( $row );
135 128 if ( count( $folder_upserts ) >= $cap ) {
136 129 $truncated = true;
137 130 break;
138 131 }
@@ -139,14 +132,17 @@
139 132 }
140 133 }
141 134
142 135 // 2) Placement upserts the viewer can see. We pull anything
143 - // written since `placements_version` whose owner is the
144 - // viewer (their own desktop) OR which lives in a folder
145 - // the viewer can see (shared content).
146 - $visible_folder_ids = array_map( static function ( $f ) {
147 - return (int) $f['id'];
148 - }, $visible );
136 + // written since `placements_version` whose owner is the
137 + // viewer (their own desktop) OR which lives in a folder
138 + // the viewer can see (shared content).
139 + $visible_folder_ids = array_map(
140 + static function ( $f ) {
141 + return (int) $f['id'];
142 + },
143 + $visible
144 + );
149 145 // Always include the desktop root (parent_id=0) for the viewer.
150 146 $placement_upserts = array();
151 147 if ( ! $truncated ) {
152 148 // Owner-or-visible-folder filter, expressed as a SINGLE
@@ -200,16 +196,16 @@
200 196 ARRAY_A
201 197 );
202 198 }
203 199 foreach ( (array) $rows as $row ) {
204 - $row = desktop_mode_files_normalize_placement_row( $row );
200 + $row = openstation_files_normalize_placement_row( $row );
205 201 // Per-placement read gate: shared folder shouldn't
206 202 // surface a row the viewer's `can_read()` rejects.
207 - $file = desktop_mode_resolve_file( $row['file_type'], $row['file_ref'] );
203 + $file = openstation_resolve_file( $row['file_type'], $row['file_ref'] );
208 204 if ( $file && ! $file->can_read( $user_id ) ) {
209 205 continue;
210 206 }
211 - $placement_upserts[] = desktop_mode_files_shape_placement( $row );
207 + $placement_upserts[] = openstation_files_shape_placement( $row );
212 208 }
213 209 if ( count( $placement_upserts ) >= $cap ) {
214 210 $truncated = true;
215 211 }
@@ -215,9 +211,9 @@
215 211 }
216 212 }
217 213
218 214 // 3) Tombstones since the last placements_version — gives the
219 - // client the "this row is gone" signal.
215 + // client the "this row is gone" signal.
220 216 $tomb_rows = $wpdb->get_results(
221 217 $wpdb->prepare(
222 218 "SELECT kind, ref_id FROM {$tables['tombstones']} WHERE removed_at_ms > %d ORDER BY removed_at_ms ASC LIMIT %d",
223 219 $placements_version,
@@ -224,9 +220,12 @@
224 220 $cap
225 221 ),
226 222 ARRAY_A
227 223 );
228 - $removed = array( 'placements' => array(), 'folders' => array() );
224 + $removed = array(
225 + 'placements' => array(),
226 + 'folders' => array(),
227 + );
229 228 foreach ( (array) $tomb_rows as $row ) {
230 229 if ( 'folder' === $row['kind'] ) {
231 230 $removed['folders'][] = (int) $row['ref_id'];
232 231 } else {
@@ -234,14 +233,14 @@
234 233 }
235 234 }
236 235
237 236 // 4) Soft-trash events. Tombstones only fire on hard delete, so
238 - // a trashed placement / folder would otherwise stay in the
239 - // client store between F5s. Surface every row whose
240 - // `trashed_at_ms` is fresher than the client's high-water
241 - // mark as a `removed.*` entry. Restoring (clearing
242 - // `trashed_at_ms`) bumps `updated_at_ms` and the row will
243 - // flow back through `placements` / `folders` upserts above.
237 + // a trashed placement / folder would otherwise stay in the
238 + // client store between F5s. Surface every row whose
239 + // `trashed_at_ms` is fresher than the client's high-water
240 + // mark as a `removed.*` entry. Restoring (clearing
241 + // `trashed_at_ms`) bumps `updated_at_ms` and the row will
242 + // flow back through `placements` / `folders` upserts above.
244 243 $trashed_placements = $wpdb->get_col(
245 244 $wpdb->prepare(
246 245 "SELECT id FROM {$tables['placements']}
247 246 WHERE trashed_at_ms IS NOT NULL
@@ -270,26 +269,26 @@
270 269 $removed['folders'][] = (int) $id;
271 270 }
272 271
273 272 // 5) Pending share invites for this viewer (across every folder
274 - // they're invited to). Owner-side share-status changes flow
275 - // through the folder upserts above; this channel is for the
276 - // recipient's "you've been invited" placeholder UI.
273 + // they're invited to). Owner-side share-status changes flow
274 + // through the folder upserts above; this channel is for the
275 + // recipient's "you've been invited" placeholder UI.
277 276 $shares = array();
278 - $sharing_enabled = function_exists( 'desktop_mode_files_sharing_enabled_for' )
279 - ? desktop_mode_files_sharing_enabled_for( $user_id )
277 + $sharing_enabled = function_exists( 'openstation_files_sharing_enabled_for' )
278 + ? openstation_files_sharing_enabled_for( $user_id )
280 279 : true;
281 - if ( $sharing_enabled && function_exists( 'desktop_mode_files_get_pending_shares_for_user' ) ) {
282 - $pending = desktop_mode_files_get_pending_shares_for_user( $user_id, $shares_version );
280 + if ( $sharing_enabled && function_exists( 'openstation_files_get_pending_shares_for_user' ) ) {
281 + $pending = openstation_files_get_pending_shares_for_user( $user_id, $shares_version );
283 282 foreach ( $pending as $row ) {
284 - $shape = desktop_mode_files_shape_share( $row );
285 - $folder = desktop_mode_files_get_folder( $row['folder_id'] );
283 + $shape = openstation_files_shape_share( $row );
284 + $folder = openstation_files_get_folder( $row['folder_id'] );
286 285 if ( $folder ) {
287 - $shape['folderName'] = (string) $folder['name'];
288 - $shape['ownerId'] = (int) $folder['owner_id'];
289 - $owner_user = get_userdata( (int) $folder['owner_id'] );
290 - $shape['ownerName'] = $owner_user ? $owner_user->display_name : '';
291 - $shape['ownerAvatar'] = $owner_user ? get_avatar_url( $owner_user->ID, array( 'size' => 48 ) ) : '';
286 + $shape['folderName'] = (string) $folder['name'];
287 + $shape['ownerId'] = (int) $folder['owner_id'];
288 + $owner_user = get_userdata( (int) $folder['owner_id'] );
289 + $shape['ownerName'] = $owner_user ? $owner_user->display_name : '';
290 + $shape['ownerAvatar'] = $owner_user ? get_avatar_url( $owner_user->ID, array( 'size' => 48 ) ) : '';
292 291 }
293 292 $shares[] = $shape;
294 293 if ( count( $shares ) >= $cap ) {
295 294 $truncated = true;
@@ -296,8 +295,22 @@
296 295 break;
297 296 }
298 297 }
299 298 }
299 + // Pending FILE-share invites ride the same channel. Shapes carry
300 + // `targetType: 'file'` + `fileId` / `fileName` so the client
301 + // invite banner can branch (folder shapes have no targetType and
302 + // default to folder handling).
303 + if ( $sharing_enabled && ! $truncated && function_exists( 'openstation_files_get_pending_file_shares_for_user' ) ) {
304 + $pending_files = openstation_files_get_pending_file_shares_for_user( $user_id, $shares_version );
305 + foreach ( $pending_files as $row ) {
306 + $shares[] = openstation_files_shape_file_share( $row );
307 + if ( count( $shares ) >= $cap ) {
308 + $truncated = true;
309 + break;
310 + }
311 + }
312 + }
300 313
301 314 // Safety net: a row that is currently being delivered as an
302 315 // upsert (alive) must NOT also appear in `removed.*`. Otherwise
303 316 // the client applies upserts first, then removals, and the
@@ -309,17 +322,19 @@
309 322 // restored but any tombstones written in error during the trash
310 323 // path stay in the table). Cleaning them up server-side prevents
311 324 // the same client-side glitch on every subsequent tick.
312 325 $upsert_placement_ids = array_map(
313 - static function ( $p ) { return (int) $p['id']; },
326 + static function ( $p ) {
327 + return (int) $p['id']; },
314 328 $placement_upserts
315 329 );
316 - $upsert_folder_ids = array_map(
317 - static function ( $f ) { return (int) $f['id']; },
330 + $upsert_folder_ids = array_map(
331 + static function ( $f ) {
332 + return (int) $f['id']; },
318 333 $folder_upserts
319 334 );
320 335 if ( ! empty( $upsert_placement_ids ) ) {
321 - $alive_placements = array_flip( $upsert_placement_ids );
336 + $alive_placements = array_flip( $upsert_placement_ids );
322 337 $removed['placements'] = array_values(
323 338 array_filter(
324 339 $removed['placements'],
325 340 static function ( $id ) use ( $alive_placements ) {
@@ -329,12 +344,12 @@
329 344 );
330 345 // Cleanup: drop any tombstones referring to placement ids
331 346 // that are demonstrably alive in this tick. Bounded by the
332 347 // upsert set so the work is per-tick, not table-wide.
333 - desktop_mode_files_purge_stale_tombstones( 'placement', $upsert_placement_ids );
348 + openstation_files_purge_stale_tombstones( 'placement', $upsert_placement_ids );
334 349 }
335 350 if ( ! empty( $upsert_folder_ids ) ) {
336 - $alive_folders = array_flip( $upsert_folder_ids );
351 + $alive_folders = array_flip( $upsert_folder_ids );
337 352 $removed['folders'] = array_values(
338 353 array_filter(
339 354 $removed['folders'],
340 355 static function ( $id ) use ( $alive_folders ) {
@@ -341,9 +356,9 @@
341 356 return ! isset( $alive_folders[ (int) $id ] );
342 357 }
343 358 )
344 359 );
345 - desktop_mode_files_purge_stale_tombstones( 'folder', $upsert_folder_ids );
360 + openstation_files_purge_stale_tombstones( 'folder', $upsert_folder_ids );
346 361 }
347 362
348 363 return array(
349 364 'placements' => $placement_upserts,
@@ -351,9 +366,9 @@
351 366 'removed' => $removed,
352 367 'shares' => array(
353 368 'pending' => $shares,
354 369 ),
355 - 'serverTimeMs' => desktop_mode_files_now_ms(),
370 + 'serverTimeMs' => openstation_files_now_ms(),
356 371 'truncated' => $truncated,
357 372 );
358 373 }
359 374
@@ -363,19 +378,17 @@
363 378 * `trashed_at_ms`). One-shot cleanup of stale rows written by
364 379 * earlier buggy code paths — once removed, the heartbeat no longer
365 380 * surfaces them every tick.
366 381 *
367 - * @since 0.8.5
368 - *
369 382 * @param string $kind 'placement' | 'folder'.
370 383 * @param int[] $ids Ids known to be alive in the current tick.
371 384 */
372 -function desktop_mode_files_purge_stale_tombstones( $kind, $ids ) {
385 +function openstation_files_purge_stale_tombstones( $kind, $ids ) {
373 386 if ( empty( $ids ) ) {
374 387 return;
375 388 }
376 389 global $wpdb;
377 - $tables = desktop_mode_files_table_names();
390 + $tables = openstation_files_table_names();
378 391 $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
379 392 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
380 393 $wpdb->query(
381 394 $wpdb->prepare(