86 lines
4.3 KiB
PHP
86 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use app\common\service\mobilerecording\MobileRecordingCos;
|
|
|
|
/** In-memory synthetic COS. No configuration, credentials, SDK or network access. */
|
|
final class FakeMobileRecordingCos extends MobileRecordingCos
|
|
{
|
|
public array $current = ['bucket' => 'synthetic-1250000000', 'region' => 'ap-guangzhou'];
|
|
public array $objects = [];
|
|
public array $multipart = [];
|
|
public array $calls = [];
|
|
public int $losePartAck = 0;
|
|
public int $loseFinishAck = 0;
|
|
public int $failHead = 0;
|
|
public int $denyHead = 0;
|
|
public int $failDelete = 0;
|
|
public int $loseCreateAck = 0;
|
|
public int $partWrites = 0;
|
|
public int $finishes = 0;
|
|
public array $deletedVersions = [];
|
|
public $onPart = null;
|
|
public function location(): array { return $this->current; }
|
|
public function key(array $o): string { return $o['bucket'] . '|' . $o['region'] . '|' . $o['object_key']; }
|
|
public function initiate(array $o): string
|
|
{
|
|
$id = 'fake-' . (count($this->multipart) + 1);
|
|
$this->multipart[$id] = ['object' => $o, 'parts' => []];
|
|
$this->calls[] = 'initiate';
|
|
if ($this->loseCreateAck-- > 0) { throw new RuntimeException('synthetic create acknowledgement lost'); }
|
|
return $id;
|
|
}
|
|
public function part(array $o, int $number, string $cipher): string
|
|
{
|
|
if (!isset($this->multipart[$o['multipart_id']])) { throw new RuntimeException('synthetic session missing'); }
|
|
$this->multipart[$o['multipart_id']]['parts'][$number] = $cipher;
|
|
$this->partWrites++; $this->calls[] = 'part';
|
|
if ($this->onPart !== null) { $callback = $this->onPart; $this->onPart = null; $callback(); }
|
|
if ($this->losePartAck-- > 0) { throw new RuntimeException('synthetic part acknowledgement lost'); }
|
|
return md5($cipher);
|
|
}
|
|
public function finish(array $o, array $parts): void
|
|
{
|
|
$session = $this->multipart[$o['multipart_id']] ?? null;
|
|
if (!$session) { throw new RuntimeException('synthetic multipart absent'); }
|
|
$bytes = '';
|
|
foreach ($parts as $part) {
|
|
$body = $session['parts'][$part['PartNumber']] ?? '';
|
|
if (md5($body) !== $part['ETag']) { throw new RuntimeException('synthetic part mismatch'); }
|
|
$bytes .= $body;
|
|
}
|
|
$this->objects[$this->key($o)] = ['bytes' => $bytes, 'context' => $o['context_hash'], 'version_id' => 'version-' . (++$this->finishes)];
|
|
unset($this->multipart[$o['multipart_id']]);
|
|
$this->calls[] = 'finish';
|
|
if ($this->loseFinishAck-- > 0) { throw new RuntimeException('synthetic finish acknowledgement lost'); }
|
|
}
|
|
public function head(array $o, bool $pinned = false): ?array
|
|
{
|
|
$this->calls[] = 'head';
|
|
if ($this->denyHead-- > 0) { throw new DomainException('云端存储授权不可用,请联系管理员检查配置'); }
|
|
if ($this->failHead-- > 0) { throw new RuntimeException('synthetic COS down'); }
|
|
$object = $this->objects[$this->key($o)] ?? null;
|
|
if (!$object || ($pinned && $o['version_id'] !== '' && $o['version_id'] !== $object['version_id'])) { return null; }
|
|
return ['size' => strlen($object['bytes']), 'context' => $object['context'], 'version_id' => $object['version_id']];
|
|
}
|
|
public function range(array $o, int $start, int $end): string
|
|
{
|
|
$object = $this->objects[$this->key($o)] ?? null;
|
|
if (!$object || $o['version_id'] !== $object['version_id']) { throw new RuntimeException('synthetic pinned object absent'); }
|
|
if ($end - $start + 1 > 1048592) { throw new RuntimeException('unbounded cloud read'); }
|
|
$this->calls[] = 'range:' . $start . '-' . $end;
|
|
return substr($object['bytes'], $start, $end - $start + 1);
|
|
}
|
|
public function delete(array $o): void
|
|
{
|
|
if ($this->failDelete-- > 0) { throw new RuntimeException('synthetic delete failed'); }
|
|
$object = $this->objects[$this->key($o)] ?? null;
|
|
if ($object && $object['version_id'] !== $o['version_id']) { throw new RuntimeException('unversioned delete forbidden in fixture'); }
|
|
$this->deletedVersions[] = $o['version_id'];
|
|
unset($this->objects[$this->key($o)]);
|
|
$this->calls[] = 'delete';
|
|
}
|
|
public function abort(array $o): void { unset($this->multipart[$o['multipart_id']]); $this->calls[] = 'abort'; }
|
|
}
|