PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
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 0.8.7 All 34 releases
← All changes | includes/desktop-files/schema.php +81 -86 0.9.71.1.10 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Files-on-the-Desktop schema.
3 + * OpenStation — Files-on-the-Desktop schema.
4 4 *
5 5 * Five custom tables back the system:
6 6 *
7 7 * - `_desktop_mode_file_placements` — every (user, parent_folder,
@@ -31,27 +31,37 @@
31 31 * denies individually; the shares row itself stays `pending`).
32 32 *
33 33 * dbDelta is the only safe path for schema migrations against the
34 34 * Core tables environment — Phase 6 re-uses this file by bumping
35 - * `DESKTOP_MODE_FILES_SCHEMA_VERSION` and adding columns.
35 + * `OPENSTATION_FILES_SCHEMA_VERSION` and adding columns.
36 36 *
37 - * @package WPDesktopMode
38 - * @since 0.9.0
37 + * @package OpenStation
39 38 */
40 39
41 40 defined( 'ABSPATH' ) || exit;
42 41
43 -define( 'DESKTOP_MODE_FILES_SCHEMA_VERSION', '13' );
44 -define( 'DESKTOP_MODE_FILES_SCHEMA_OPTION', 'desktop_mode_files_schema_version' );
42 +define( 'OPENSTATION_FILES_SCHEMA_VERSION', '13' );
43 +/**
44 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
45 + * persisted or externally-visible identifier, so renaming it would
46 + * orphan data already written by live installs (or break a live
47 + * URL). The mismatch between this constant's name and its value is
48 + * deliberate — it is NOT a half-finished rename.
49 + */
50 +define( 'OPENSTATION_FILES_SCHEMA_OPTION', 'desktop_mode_files_schema_version' );
45 51
46 52 /**
47 53 * Returns the per-table names with the active prefix applied.
48 54 *
49 - * @since 0.9.0
55 + * The `desktop_mode_` segment is the pre-rebrand spelling and is frozen:
56 + * these are real tables holding real rows on live installs. Renaming
57 + * them silently creates a second, empty set and every desktop icon,
58 + * folder and uploaded file disappears. The mismatch against the
59 + * `openstation_*` function name is deliberate.
50 60 *
51 61 * @return array{ placements: string, folders: string, tombstones: string, shares: string, decisions: string }
52 62 */
53 -function desktop_mode_files_table_names() {
63 +function openstation_files_table_names() {
54 64 global $wpdb;
55 65 return array(
56 66 'placements' => $wpdb->prefix . 'desktop_mode_file_placements',
57 67 'folders' => $wpdb->prefix . 'desktop_mode_folders',
@@ -65,20 +75,18 @@
65 75 /**
66 76 * Idempotent `dbDelta` call. Hooked on plugin activation and on
67 77 * `admin_init` (gated by a version-option mismatch) so a manual
68 78 * file copy install still ends up with the tables.
69 - *
70 - * @since 0.9.0
71 79 */
72 -function desktop_mode_files_install_schema() {
80 +function openstation_files_install_schema() {
73 81 global $wpdb;
74 82
75 83 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
76 84
77 - $tables = desktop_mode_files_table_names();
78 - $charset_collate = $wpdb->get_charset_collate();
85 + $tables = openstation_files_table_names();
86 + $charset_collate = $wpdb->get_charset_collate();
79 87
80 - // Schema v2 (since 0.8.0): adds trash columns to both placements
88 + // Schema v2: adds trash columns to both placements
81 89 // and folders so deleted shortcuts and folders land in the
82 90 // recycle bin instead of vanishing. `trashed_at_ms` is the
83 91 // epoch-ms timestamp of the trash event (NULL = active).
84 92 // `trashed_by` records the user that fired it (for permission
@@ -133,9 +141,9 @@
133 141 PRIMARY KEY (id),
134 142 KEY kind_removed (kind, removed_at_ms)
135 143 ) $charset_collate;";
136 144
137 - // Schema v13 (since 0.9.6): real per-user file storage. One row
145 + // Schema v13: real per-user file storage. One row
138 146 // per uploaded file; the bytes live flat on disk under
139 147 // `uploads/desktop-mode-files/<owner_id>/<disk_name>` with a
140 148 // server-generated extensionless `disk_name` (UUID) — hierarchy,
141 149 // naming, and sharing are entirely DB concerns (folders +
@@ -186,9 +194,9 @@
186 194 // `owner_id` as a missing column and ADD it (leaving the old
187 195 // `user_id` in place + the new `owner_id` NULL). Running the
188 196 // CHANGE COLUMN first means dbDelta sees the table already
189 197 // matches the desired shape.
190 - desktop_mode_files_rename_user_id_to_owner_id();
198 + openstation_files_rename_user_id_to_owner_id();
191 199
192 200 dbDelta( $placements_sql );
193 201 dbDelta( $folders_sql );
194 202 dbDelta( $tombstones_sql );
@@ -197,9 +205,9 @@
197 205 // dbDelta has well-documented quirks with `NULL`-only columns
198 206 // (no DEFAULT) — under some MySQL/MariaDB combos it silently
199 207 // skips the ADD COLUMN. Verify the v2 trash columns are
200 208 // physically present and ALTER them in directly when not.
201 - desktop_mode_files_ensure_trash_columns();
209 + openstation_files_ensure_trash_columns();
202 210
203 211 // v4: clean up duplicate placements created by sessions that
204 212 // hit the auto-orphan-placer while the v2 trash columns were
205 213 // missing — every `WHERE trashed_at_ms IS NULL` precheck
@@ -206,15 +214,15 @@
206 214 // returned empty, so each pageload re-inserted every
207 215 // registered shortcut. Collapse runs of identical
208 216 // `(owner_id, parent_id, file_type, file_ref)` rows down to
209 217 // the lowest id.
210 - desktop_mode_files_dedupe_placements();
218 + openstation_files_dedupe_placements();
211 219
212 220 // v5: enforce uniqueness at the DB level so a future bug
213 221 // (or a racing pair of REST requests) can never re-create
214 222 // the duplicate shortcuts again. Must run AFTER dedupe —
215 223 // adding a unique key against duplicate rows would fail.
216 - desktop_mode_files_ensure_unique_placement_index();
224 + openstation_files_ensure_unique_placement_index();
217 225
218 226 // v9: belt-and-suspenders existence check for the shares +
219 227 // decisions tables. The folder-sharing feature is the
220 228 // canonical source of truth for "who can see this folder" —
@@ -220,10 +228,10 @@
220 228 // canonical source of truth for "who can see this folder" —
221 229 // the `share_meta` JSON column on the folders table remains
222 230 // for diagnostic purposes only and is not consulted by the
223 231 // visibility resolver.
224 - desktop_mode_files_ensure_shares_table();
225 - desktop_mode_files_ensure_decisions_table();
232 + openstation_files_ensure_shares_table();
233 + openstation_files_ensure_decisions_table();
226 234
227 235 // v10: `updated_by` column on placements so the If-Match 409
228 236 // conflict toast names the SESSION that actually won the race,
229 237 // not just whoever currently owns the row. Critical for the
@@ -229,20 +237,18 @@
229 237 // not just whoever currently owns the row. Critical for the
230 238 // shared-write scenario where User B (writer recipient) moves a
231 239 // placement and User C gets the conflict — without this column,
232 240 // the toast would blame User A (owner of the row).
233 - desktop_mode_files_ensure_updated_by_column();
241 + openstation_files_ensure_updated_by_column();
234 242
235 - update_option( DESKTOP_MODE_FILES_SCHEMA_OPTION, DESKTOP_MODE_FILES_SCHEMA_VERSION );
243 + update_option( OPENSTATION_FILES_SCHEMA_OPTION, OPENSTATION_FILES_SCHEMA_VERSION );
236 244
237 245 /**
238 246 * Fires after the files schema is installed / migrated.
239 247 *
240 - * @since 0.9.0
241 - *
242 248 * @param string $version The version that was installed.
243 249 */
244 - do_action( 'desktop_mode_files_schema_installed', DESKTOP_MODE_FILES_SCHEMA_VERSION );
250 + do_action( 'openstation_files_schema_installed', OPENSTATION_FILES_SCHEMA_VERSION );
245 251 }
246 252
247 253 /**
248 254 * Belt-and-suspenders verifier for the v2 trash columns. Reads
@@ -249,14 +255,13 @@
249 255 * `INFORMATION_SCHEMA.COLUMNS` for the placements + folders tables
250 256 * and `ALTER`s in any column dbDelta missed. Idempotent: each
251 257 * `ALTER` only fires when the column is not already there.
252 258 *
253 - * @since 0.8.0
254 259 * @internal
255 260 */
256 -function desktop_mode_files_ensure_trash_columns() {
261 +function openstation_files_ensure_trash_columns() {
257 262 global $wpdb;
258 - $tables = desktop_mode_files_table_names();
263 + $tables = openstation_files_table_names();
259 264
260 265 // Two-worker race protection: between the INFORMATION_SCHEMA
261 266 // check and the ALTER, a concurrent worker (cron + admin-init,
262 267 // REST + heartbeat) can run the same check, see the column
@@ -269,12 +274,12 @@
269 274 $ensure = static function ( $table, $column, $definition ) use ( $wpdb ) {
270 275 $col_exists = static function () use ( $wpdb, $table, $column ) {
271 276 return (int) $wpdb->get_var(
272 277 $wpdb->prepare(
273 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
278 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
274 279 WHERE TABLE_SCHEMA = DATABASE()
275 280 AND TABLE_NAME = %s
276 - AND COLUMN_NAME = %s",
281 + AND COLUMN_NAME = %s',
277 282 $table,
278 283 $column
279 284 )
280 285 );
@@ -294,18 +299,18 @@
294 299 $wpdb->query( "ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}" );
295 300 }
296 301 };
297 302
298 - $ensure( $tables['placements'], 'trashed_at_ms', 'BIGINT UNSIGNED NULL' );
299 - $ensure( $tables['placements'], 'trashed_by', 'BIGINT UNSIGNED NULL' );
303 + $ensure( $tables['placements'], 'trashed_at_ms', 'BIGINT UNSIGNED NULL' );
304 + $ensure( $tables['placements'], 'trashed_by', 'BIGINT UNSIGNED NULL' );
300 305 $ensure( $tables['placements'], 'trashed_via_folder', 'BIGINT UNSIGNED NULL' );
301 306 // v6: ancestry snapshot — JSON capturing every folder in the
302 307 // parent chain at trash time so a restore can resurrect the
303 308 // chain even when a folder was hard-deleted in the meantime.
304 - $ensure( $tables['placements'], 'trashed_meta', 'LONGTEXT NULL' );
305 - $ensure( $tables['folders'], 'trashed_at_ms', 'BIGINT UNSIGNED NULL' );
306 - $ensure( $tables['folders'], 'trashed_by', 'BIGINT UNSIGNED NULL' );
307 - $ensure( $tables['folders'], 'trashed_meta', 'LONGTEXT NULL' );
309 + $ensure( $tables['placements'], 'trashed_meta', 'LONGTEXT NULL' );
310 + $ensure( $tables['folders'], 'trashed_at_ms', 'BIGINT UNSIGNED NULL' );
311 + $ensure( $tables['folders'], 'trashed_by', 'BIGINT UNSIGNED NULL' );
312 + $ensure( $tables['folders'], 'trashed_meta', 'LONGTEXT NULL' );
308 313 }
309 314
310 315 /**
311 316 * Collapse duplicate `(owner_id, parent_id, file_type, file_ref)`
@@ -316,17 +321,16 @@
316 321 * covers EVERY file type, so duplicates of any type within the
317 322 * same (owner, parent) are disallowed at the DB level; if legacy
318 323 * duplicates of another type exist, the (error-suppressed)
319 324 * `ADD UNIQUE` in
320 - * `desktop_mode_files_ensure_unique_placement_index()` will fail
325 + * `openstation_files_ensure_unique_placement_index()` will fail
321 326 * and leave the index absent until those rows are cleaned up.
322 327 *
323 - * @since 0.8.0
324 328 * @internal
325 329 */
326 -function desktop_mode_files_dedupe_placements() {
330 +function openstation_files_dedupe_placements() {
327 331 global $wpdb;
328 - $tables = desktop_mode_files_table_names();
332 + $tables = openstation_files_table_names();
329 333 $tbl = $tables['placements'];
330 334
331 335 // Once the unique index exists, MySQL prevents duplicate
332 336 // inserts at the DB level — dedupe is a no-op and the
@@ -334,12 +338,12 @@
334 338 // call. Skip in that case so the cost is paid exactly once,
335 339 // during the v4 → v5 migration.
336 340 $has_unique = (int) $wpdb->get_var(
337 341 $wpdb->prepare(
338 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
342 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
339 343 WHERE TABLE_SCHEMA = DATABASE()
340 344 AND TABLE_NAME = %s
341 - AND INDEX_NAME = %s",
345 + AND INDEX_NAME = %s',
342 346 $tbl,
343 347 'placement_unique'
344 348 )
345 349 );
@@ -373,22 +377,21 @@
373 377 * Note: `file_ref` is `VARCHAR(255)` — combined with the three
374 378 * other columns this fits comfortably under MySQL's 3072-byte
375 379 * InnoDB index-key limit on `utf8mb4`.
376 380 *
377 - * @since 0.8.0
378 381 * @internal
379 382 */
380 -function desktop_mode_files_ensure_unique_placement_index() {
383 +function openstation_files_ensure_unique_placement_index() {
381 384 global $wpdb;
382 - $tables = desktop_mode_files_table_names();
385 + $tables = openstation_files_table_names();
383 386 $tbl = $tables['placements'];
384 387
385 388 $exists = (int) $wpdb->get_var(
386 389 $wpdb->prepare(
387 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
390 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
388 391 WHERE TABLE_SCHEMA = DATABASE()
389 392 AND TABLE_NAME = %s
390 - AND INDEX_NAME = %s",
393 + AND INDEX_NAME = %s',
391 394 $tbl,
392 395 'placement_unique'
393 396 )
394 397 );
@@ -411,9 +414,9 @@
411 414 /**
412 415 * Add the v10 `updated_by` column to the placements table.
413 416 *
414 417 * Tracks which user last mutated the row (created, moved, restored).
415 - * Used by `desktop_mode_files_check_if_match()` so the If-Match 409
418 + * Used by `openstation_files_check_if_match()` so the If-Match 409
416 419 * conflict toast attributes the change to the SESSION that won the
417 420 * race rather than to the row's static owner — critical when a
418 421 * writer recipient of a shared folder rearranges placements and
419 422 * another viewer hits a stale `If-Match`.
@@ -420,21 +423,20 @@
420 423 *
421 424 * NULL on legacy rows (pre-v10). The conflict resolver falls back
422 425 * to `owner_id` when this column is NULL, matching the old behavior.
423 426 *
424 - * @since 0.8.5 (schema v10)
425 427 * @internal
426 428 */
427 -function desktop_mode_files_ensure_updated_by_column() {
429 +function openstation_files_ensure_updated_by_column() {
428 430 global $wpdb;
429 - $tables = desktop_mode_files_table_names();
431 + $tables = openstation_files_table_names();
430 432 $tbl = $tables['placements'];
431 433 $exists = (int) $wpdb->get_var(
432 434 $wpdb->prepare(
433 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
435 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
434 436 WHERE TABLE_SCHEMA = DATABASE()
435 437 AND TABLE_NAME = %s
436 - AND COLUMN_NAME = %s",
438 + AND COLUMN_NAME = %s',
437 439 $tbl,
438 440 'updated_by'
439 441 )
440 442 );
@@ -452,9 +454,9 @@
452 454
453 455 /**
454 456 * Rename `placements.user_id` to `placements.owner_id` and the
455 457 * matching `user_parent` index to `owner_parent`. The two
456 - * desktop-mode-owned tables historically used different names for
458 + * os-owned tables historically used different names for
457 459 * the same "row's owner" concept — folders carried `owner_id` from
458 460 * day one, placements carried `user_id`. v11 unifies them so SQL
459 461 * and PHP read identically across the two tables.
460 462 *
@@ -462,9 +464,9 @@
462 464 * dbDelta runs after this and creates the table with the new
463 465 * column name directly) and when the column is already renamed.
464 466 *
465 467 * Must run BEFORE `dbDelta( $placements_sql )` in
466 - * `desktop_mode_files_install_schema()` — dbDelta does NOT rename
468 + * `openstation_files_install_schema()` — dbDelta does NOT rename
467 469 * columns, so against a v≤10 table whose definition says
468 470 * `owner_id` it would ADD a new `owner_id` column and leave the
469 471 * stale `user_id` in place. Running the CHANGE COLUMN first puts
470 472 * the table in the desired shape so dbDelta sees no diff.
@@ -474,14 +476,13 @@
474 476 * partial run during dev iteration) gets a clean retry. The
475 477 * function is idempotent — early-returns when `user_id` is absent,
476 478 * so healthy v11 installs see a cheap no-op on the retry.
477 479 *
478 - * @since 0.8.9 (schema v11, redelivered at v12)
479 480 * @internal
480 481 */
481 -function desktop_mode_files_rename_user_id_to_owner_id() {
482 +function openstation_files_rename_user_id_to_owner_id() {
482 483 global $wpdb;
483 - $tables = desktop_mode_files_table_names();
484 + $tables = openstation_files_table_names();
484 485 $tbl = $tables['placements'];
485 486
486 487 // Fresh install — the table doesn't exist yet; dbDelta creates
487 488 // it with `owner_id` directly. Nothing to migrate.
@@ -486,10 +487,10 @@
486 487 // Fresh install — the table doesn't exist yet; dbDelta creates
487 488 // it with `owner_id` directly. Nothing to migrate.
488 489 $table_exists = (int) $wpdb->get_var(
489 490 $wpdb->prepare(
490 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
491 - WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s",
491 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
492 + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
492 493 $tbl
493 494 )
494 495 );
495 496 if ( 0 === $table_exists ) {
@@ -548,22 +549,21 @@
548 549 * declarations on a non-`utf8mb4` collation gets silently skipped
549 550 * on some MySQL/MariaDB combos; we mirror the trash-columns
550 551 * pattern and `CREATE TABLE IF NOT EXISTS` the row explicitly.
551 552 *
552 - * @since 0.8.5
553 553 * @internal
554 554 */
555 -function desktop_mode_files_ensure_shares_table() {
555 +function openstation_files_ensure_shares_table() {
556 556 global $wpdb;
557 - $tables = desktop_mode_files_table_names();
557 + $tables = openstation_files_table_names();
558 558 $charset_collate = $wpdb->get_charset_collate();
559 559 $tbl = $tables['shares'];
560 560
561 561 $exists = (int) $wpdb->get_var(
562 562 $wpdb->prepare(
563 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
563 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
564 564 WHERE TABLE_SCHEMA = DATABASE()
565 - AND TABLE_NAME = %s",
565 + AND TABLE_NAME = %s',
566 566 $tbl
567 567 )
568 568 );
569 569 if ( 0 === $exists ) {
@@ -590,12 +590,12 @@
590 590 // Existing table — make sure `target_type` is there for
591 591 // installs that ran a pre-target_type build of v8.
592 592 $has_col = (int) $wpdb->get_var(
593 593 $wpdb->prepare(
594 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
594 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
595 595 WHERE TABLE_SCHEMA = DATABASE()
596 596 AND TABLE_NAME = %s
597 - AND COLUMN_NAME = %s",
597 + AND COLUMN_NAME = %s',
598 598 $tbl,
599 599 'target_type'
600 600 )
601 601 );
@@ -613,22 +613,21 @@
613 613
614 614 /**
615 615 * Belt-and-suspenders verifier for the decisions table.
616 616 *
617 - * @since 0.8.5
618 617 * @internal
619 618 */
620 -function desktop_mode_files_ensure_decisions_table() {
619 +function openstation_files_ensure_decisions_table() {
621 620 global $wpdb;
622 - $tables = desktop_mode_files_table_names();
621 + $tables = openstation_files_table_names();
623 622 $charset_collate = $wpdb->get_charset_collate();
624 623 $tbl = $tables['decisions'];
625 624
626 625 $exists = (int) $wpdb->get_var(
627 626 $wpdb->prepare(
628 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
627 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
629 628 WHERE TABLE_SCHEMA = DATABASE()
630 - AND TABLE_NAME = %s",
629 + AND TABLE_NAME = %s',
631 630 $tbl
632 631 )
633 632 );
634 633 if ( 0 === $exists ) {
@@ -651,34 +650,30 @@
651 650 /**
652 651 * Lazy migrator — runs on `admin_init` when the stored schema
653 652 * version doesn't match the constant. Idempotent: `dbDelta`
654 653 * itself is a no-op when the table already matches.
655 - *
656 - * @since 0.9.0
657 654 */
658 -function desktop_mode_files_maybe_install_schema() {
659 - $installed = get_option( DESKTOP_MODE_FILES_SCHEMA_OPTION, '' );
660 - if ( $installed === DESKTOP_MODE_FILES_SCHEMA_VERSION ) {
655 +function openstation_files_maybe_install_schema() {
656 + $installed = get_option( OPENSTATION_FILES_SCHEMA_OPTION, '' );
657 + if ( OPENSTATION_FILES_SCHEMA_VERSION === $installed ) {
661 658 return;
662 659 }
663 - desktop_mode_files_install_schema();
660 + openstation_files_install_schema();
664 661 }
665 -add_action( 'admin_init', 'desktop_mode_files_maybe_install_schema' );
662 +add_action( 'admin_init', 'openstation_files_maybe_install_schema' );
666 663 // REST + front-end requests never fire `admin_init` — without these
667 664 // hooks a session that hits a REST endpoint before any admin page
668 665 // load would query the placements / folders tables before the v2
669 666 // trash columns exist, throwing wpdb errors and blanking the desktop.
670 -add_action( 'rest_api_init', 'desktop_mode_files_maybe_install_schema' );
671 -add_action( 'init', 'desktop_mode_files_maybe_install_schema', 1 );
672 -register_activation_hook( DESKTOP_MODE_FILE, 'desktop_mode_files_install_schema' );
667 +add_action( 'rest_api_init', 'openstation_files_maybe_install_schema' );
668 +add_action( 'init', 'openstation_files_maybe_install_schema', 1 );
669 +register_activation_hook( OPENSTATION_FILE, 'openstation_files_install_schema' );
673 670
674 671 /**
675 672 * Current epoch-ms timestamp. Centralized so the store and the
676 673 * tombstone writer stay in lock-step.
677 674 *
678 - * @since 0.9.0
679 - *
680 675 * @return int
681 676 */
682 -function desktop_mode_files_now_ms() {
677 +function openstation_files_now_ms() {
683 678 return (int) round( microtime( true ) * 1000 );
684 679 }