table = $wpdb->prefix . 'shiftora_transfer_sizing'; } /** * Load the sizing state for a stage, or the conservative default when nothing is * recorded or the row cannot be read. * * @param int|null $encoded_ceiling_bytes Receiver limit this store sizes against. */ public function load( int $migration_id, string $stage, ?int $encoded_ceiling_bytes = null ): TransferSizer { $wpdb = $this->wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom table, fresh read required $row = $wpdb->get_row( $wpdb->prepare( 'SELECT budget_bytes, fast_streak, bytes_per_second FROM %i WHERE migration_id = %d AND stage = %s', $this->table, $migration_id, $stage ), ARRAY_A ); if ( ! is_array( $row ) ) { // No row for this migration yet, the ordinary first-run case. The start is // what the last run on this stage converged on, so a steady site does not // relearn its link size on every migration. return TransferSizer::from_state( TransferSizer::learned_default( $stage ), 0, 0.0, $encoded_ceiling_bytes ); } return TransferSizer::from_state( (int) ( $row['budget_bytes'] ?? 0 ), (int) ( $row['fast_streak'] ?? 0 ), // bytes_per_second is stored unsigned, so a rate is an integer. (float) ( $row['bytes_per_second'] ?? 0 ), $encoded_ceiling_bytes ); } /** * Persist the state so the next worker inherits it. */ public function save( int $migration_id, string $stage, TransferSizer $sizer ): void { $wpdb = $this->wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom table, fresh read required $wpdb->query( $wpdb->prepare( 'INSERT INTO %i (migration_id, stage, budget_bytes, fast_streak, bytes_per_second, updated_at) VALUES (%d, %s, %d, %d, %d, %s) ON DUPLICATE KEY UPDATE budget_bytes = VALUES(budget_bytes), fast_streak = VALUES(fast_streak), bytes_per_second = VALUES(bytes_per_second), updated_at = VALUES(updated_at)', $this->table, $migration_id, $stage, $sizer->budget_bytes(), $sizer->fast_streak(), (int) round( $sizer->measured_bytes_per_second() ), gmdate( 'Y-m-d H:i:s' ) ) ); } /** * Remember what a stage settled on, so the next migration opens there. * * Only a run that finished with every file transferred may lower the stored value. * A failing run shows its last request was too big, not that the link cannot carry * what it did before. Growth is always safe to keep. * * @param string $stage Stage key. */ public function remember_settled( string $stage, TransferSizer $sizer, bool $completed ): void { if ( ! $completed ) { return; } $stored = get_option( TransferSizer::option_name( $stage ), 0 ); // Raised or held, never lowered below what earlier runs proved. if ( ! is_numeric( $stored ) || $sizer->budget_bytes() > (int) $stored ) { $sizer->remember_as_default( $stage ); } } /** * Drop a migration's sizing rows when it reaches a terminal state, so the table * does not grow forever and the diagnostic that counts its rows stays accurate. */ public function forget( int $migration_id ): void { $wpdb = $this->wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom table, fresh read required $wpdb->query( $wpdb->prepare( 'DELETE FROM %i WHERE migration_id = %d', $this->table, $migration_id ) ); } }