mirror of
https://github.com/nextcloud/server.git
synced 2026-10-08 20:51:14 +02:00
PHP 8.3 allows class constants to declare a type. Adopt it where doing so cannot break third-party apps. Typing an inheritable constant is a hard BC break: a subclass that redeclares it untyped fails to load with "Type of C::FOO must be compatible with P::FOO of type string". Changes are therefore limited to constants that cannot be redeclared by a subclass: - private const (not inherited) - final public/protected const - constants declared in a final class or an enum Interface constants, trait constants, and public/protected constants in non-final (including abstract) classes are left untyped, as is all of lib/public (OCP) and lib/unstable (NCU). One review-requested exception: the public OBJECT_PREFIX/OBJECT_SUFFIX constants of the app-internal CalDAV import helpers (TextImporter, XmlImporter) are typed as well; these classes are not public API and have no subclasses. Only string, int and array are used. float is avoided because it would silently coerce an int literal and change === comparisons. No constant name or value is modified: every changed line adds only the type token, so runtime behaviour is unchanged. Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com>
114 lines
2.7 KiB
PHP
114 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
namespace OC\Snowflake;
|
|
|
|
use OC_Util;
|
|
use OCP\ITempManager;
|
|
use Override;
|
|
|
|
class FileSequence implements ISequence {
|
|
/** Number of files to use */
|
|
private const int NB_FILES = 20;
|
|
/** Lock file directory **/
|
|
public const LOCK_FILE_DIRECTORY = 'sfi_file_sequence';
|
|
/** Lock filename format **/
|
|
private const string LOCK_FILE_FORMAT = 'seq-%03d.lock';
|
|
/** Delete sequences after SEQUENCE_TTL seconds **/
|
|
private const int SEQUENCE_TTL = 30;
|
|
|
|
private string $workDir;
|
|
|
|
public function __construct(
|
|
ITempManager $tempManager,
|
|
) {
|
|
$this->workDir = $tempManager->getTempBaseDir() . '/' . self::LOCK_FILE_DIRECTORY . '_' . OC_Util::getInstanceId();
|
|
$this->ensureWorkdirExists();
|
|
}
|
|
|
|
private function ensureWorkdirExists(): void {
|
|
if (is_dir($this->workDir)) {
|
|
if (!is_writable($this->workDir)) {
|
|
throw new \Exception('File sequence directory exists but is not writable');
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (@mkdir($this->workDir, 0700)) {
|
|
return;
|
|
}
|
|
|
|
// Maybe the directory was created in the meantime
|
|
if (is_dir($this->workDir)) {
|
|
return;
|
|
}
|
|
|
|
throw new \Exception('Fail to create file sequence directory');
|
|
}
|
|
|
|
#[Override]
|
|
public function isAvailable(): bool {
|
|
return true;
|
|
}
|
|
|
|
#[Override]
|
|
public function nextId(int $serverId, int $seconds, int $milliseconds): int {
|
|
// Open lock file
|
|
$filePath = $this->getFilePath($milliseconds % self::NB_FILES);
|
|
$fp = fopen($filePath, 'c+');
|
|
if ($fp === false) {
|
|
throw new \Exception('Unable to open sequence ID file: ' . $filePath);
|
|
}
|
|
if (!flock($fp, LOCK_EX)) {
|
|
throw new \Exception('Unable to acquire lock on sequence ID file: ' . $filePath);
|
|
}
|
|
|
|
// Read content
|
|
$content = (string)fgets($fp);
|
|
$locks = $content === ''
|
|
? []
|
|
: json_decode($content, true, 3, JSON_THROW_ON_ERROR);
|
|
|
|
// Generate new ID
|
|
if (isset($locks[$seconds])) {
|
|
if (isset($locks[$seconds][$milliseconds])) {
|
|
++$locks[$seconds][$milliseconds];
|
|
} else {
|
|
$locks[$seconds][$milliseconds] = 0;
|
|
}
|
|
} else {
|
|
$locks[$seconds] = [
|
|
$milliseconds => 0
|
|
];
|
|
}
|
|
|
|
// Clean old sequence IDs
|
|
$cleanBefore = $seconds - self::SEQUENCE_TTL;
|
|
$locks = array_filter($locks, static function ($key) use ($cleanBefore) {
|
|
return $key >= $cleanBefore;
|
|
}, ARRAY_FILTER_USE_KEY);
|
|
|
|
// Write data
|
|
ftruncate($fp, 0);
|
|
$content = json_encode($locks, JSON_THROW_ON_ERROR);
|
|
rewind($fp);
|
|
fwrite($fp, $content);
|
|
fsync($fp);
|
|
|
|
// Release lock
|
|
fclose($fp);
|
|
|
|
return $locks[$seconds][$milliseconds];
|
|
}
|
|
|
|
private function getFilePath(int $fileId): string {
|
|
return $this->workDir . '/' . sprintf(self::LOCK_FILE_FORMAT, $fileId);
|
|
}
|
|
}
|