mirror of
https://github.com/icewind1991/lockpick.git
synced 2026-06-03 09:14:08 +02:00
initial version
This commit is contained in:
commit
4b90444fa3
9 changed files with 654 additions and 0 deletions
23
appinfo/info.xml
Normal file
23
appinfo/info.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<info>
|
||||||
|
<id>lockpick</id>
|
||||||
|
<name>Lock pick</name>
|
||||||
|
<summary>Transaction locking debuging</summary>
|
||||||
|
<licence>AGPL</licence>
|
||||||
|
<author>Robin Appelman</author>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
<types>
|
||||||
|
<filesystem/>
|
||||||
|
<logging/>
|
||||||
|
</types>
|
||||||
|
<namespace>LockPick</namespace>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<nextcloud min-version="24" max-version="25"/>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<commands>
|
||||||
|
<command>OCA\LockPick\Command\Show</command>
|
||||||
|
<command>OCA\LockPick\Command\ListCommand</command>
|
||||||
|
</commands>
|
||||||
|
</info>
|
||||||
66
lib/AppInfo/Application.php
Normal file
66
lib/AppInfo/Application.php
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\LockPick\AppInfo;
|
||||||
|
|
||||||
|
use OC\Lock\MemcacheLockingProvider;
|
||||||
|
use OC\Lock\NoopLockingProvider;
|
||||||
|
use OCA\LockPick\DebugLockingProvider;
|
||||||
|
use OCA\LockPick\TraceStore;
|
||||||
|
use OCP\AppFramework\App;
|
||||||
|
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||||
|
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||||
|
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||||
|
use OCP\ICacheFactory;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Lock\ILockingProvider;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
class Application extends App implements IBootstrap {
|
||||||
|
public function __construct(array $urlParams = []) {
|
||||||
|
parent::__construct('lockpick', $urlParams);
|
||||||
|
|
||||||
|
// we are overly aggressive and rude in how we register out locking provider to ensure it gets picked up early
|
||||||
|
\OC::$server->registerService(ILockingProvider::class, function (ContainerInterface $c) {
|
||||||
|
$config = $c->get(IConfig::class);
|
||||||
|
$ttl = $config->getSystemValue('filelocking.ttl', 3600);
|
||||||
|
if ($config->getSystemValue('filelocking.enabled', true) or (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
|
||||||
|
/** @var \OC\Memcache\Factory $memcacheFactory */
|
||||||
|
$memcacheFactory = $c->get(ICacheFactory::class);
|
||||||
|
$memcache = $memcacheFactory->createLocking('lock');
|
||||||
|
$inner = new MemcacheLockingProvider($memcache, $ttl);
|
||||||
|
return new DebugLockingProvider($inner, $c->get(IRequest::class), $c->get(TraceStore::class));
|
||||||
|
}
|
||||||
|
return new NoopLockingProvider();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(IRegistrationContext $context): void {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(IBootContext $context): void {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
||||||
66
lib/BackTraceFormatter.php
Normal file
66
lib/BackTraceFormatter.php
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\LockPick;
|
||||||
|
|
||||||
|
|
||||||
|
class BackTraceFormatter {
|
||||||
|
private function filePrefix(array $backtrace): string {
|
||||||
|
$files = array_map(function (array $item) {
|
||||||
|
return $item['file'] ?? '';
|
||||||
|
}, $backtrace);
|
||||||
|
if (count($files) < 2) {
|
||||||
|
return $files[0] ?? '';
|
||||||
|
}
|
||||||
|
$i = 0;
|
||||||
|
while (isset($files[0][$i]) && array_reduce($files, function ($every, $item) use ($files, $i) {
|
||||||
|
return $every && $item[$i] == $files[0][$i];
|
||||||
|
}, true)) {
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
return substr($files[0], 0, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function format(array $backtrace, string $indent = ""): string {
|
||||||
|
$prefixLength = strlen($this->filePrefix($backtrace));
|
||||||
|
$backtrace = array_map(function ($trace) use ($prefixLength) {
|
||||||
|
return [
|
||||||
|
'line' => isset($trace['file']) ? (substr($trace['file'], $prefixLength) . ' ' . $trace['line']) : '--',
|
||||||
|
'call' => isset($trace['class']) ? ($trace['class'] . $trace['type'] . $trace['function']) : $trace['function'],
|
||||||
|
];
|
||||||
|
}, $backtrace);
|
||||||
|
$callLength = max(array_map(function ($item) {
|
||||||
|
return strlen($item['call']);
|
||||||
|
}, $backtrace));
|
||||||
|
|
||||||
|
$output = "";
|
||||||
|
foreach ($backtrace as $trace) {
|
||||||
|
if ($output !== "") {
|
||||||
|
$output .= "\n";
|
||||||
|
}
|
||||||
|
$output .= $indent . str_pad($trace['call'], $callLength) . ' - ' . $trace['line'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/Command/ListCommand.php
Normal file
56
lib/Command/ListCommand.php
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\LockPick\Command;
|
||||||
|
|
||||||
|
use OC\Core\Command\Base;
|
||||||
|
use OCA\LockPick\TraceStore;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class ListCommand extends Base {
|
||||||
|
private TraceStore $store;
|
||||||
|
|
||||||
|
public function __construct(TraceStore $store) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->store = $store;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('lockpick:list')
|
||||||
|
->setDescription('List stored conflict traces');
|
||||||
|
parent::configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
$traces = $this->store->all();
|
||||||
|
|
||||||
|
foreach ($traces as $id => $request) {
|
||||||
|
$output->writeln("$id - $request");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
83
lib/Command/Show.php
Normal file
83
lib/Command/Show.php
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\LockPick\Command;
|
||||||
|
|
||||||
|
use OC\Core\Command\Base;
|
||||||
|
use OCA\LockPick\BackTraceFormatter;
|
||||||
|
use OCA\LockPick\TraceStore;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class Show extends Base {
|
||||||
|
private TraceStore $store;
|
||||||
|
private BackTraceFormatter $traceFormatter;
|
||||||
|
|
||||||
|
public function __construct(TraceStore $store, BackTraceFormatter $traceFormatter) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->store = $store;
|
||||||
|
$this->traceFormatter = $traceFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('lockpick:show')
|
||||||
|
->setDescription('Show the latest detected lock conflict')
|
||||||
|
->addArgument('trace_id', InputArgument::OPTIONAL, "Id of the trace to show, default to the latest trace");
|
||||||
|
parent::configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
$id = $input->getArgument('trace_id');
|
||||||
|
if ($id) {
|
||||||
|
$traces = $this->store->get((int)$id);
|
||||||
|
|
||||||
|
if (!is_array($traces)) {
|
||||||
|
$output->writeln("Trace not found");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$traces = $this->store->last();
|
||||||
|
|
||||||
|
if (!is_array($traces)) {
|
||||||
|
$output->writeln("No conflict stored");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln("<info>Conflict detected between " . count($traces) . " locks</info>");
|
||||||
|
$first = true;
|
||||||
|
foreach ($traces as $item) {
|
||||||
|
if (!$first) {
|
||||||
|
$output->writeln("");
|
||||||
|
}
|
||||||
|
$first = false;
|
||||||
|
$output->writeln($item->getTypeName() . " lock from");
|
||||||
|
$output->writeln($this->traceFormatter->format($item->getBacktrace(), " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
144
lib/DebugLockingProvider.php
Normal file
144
lib/DebugLockingProvider.php
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\LockPick;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Lock\ILockingProvider;
|
||||||
|
use OCP\Lock\LockedException;
|
||||||
|
|
||||||
|
class DebugLockingProvider implements ILockingProvider {
|
||||||
|
private ILockingProvider $inner;
|
||||||
|
private TraceStore $store;
|
||||||
|
private IRequest $request;
|
||||||
|
private array $openTraces = [];
|
||||||
|
private array $pathMap = [];
|
||||||
|
|
||||||
|
public function __construct(ILockingProvider $inner, IRequest $request, TraceStore $store) {
|
||||||
|
$this->inner = $inner;
|
||||||
|
$this->request = $request;
|
||||||
|
$this->store = $store;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize path to the "readable" form if possible
|
||||||
|
*/
|
||||||
|
private function getPath(string $path, string $readablePath = null): string {
|
||||||
|
if ($readablePath) {
|
||||||
|
$this->pathMap[$path] = $readablePath;
|
||||||
|
return $readablePath;
|
||||||
|
} elseif (isset($this->pathMap[$path])) {
|
||||||
|
return $this->pathMap[$path];
|
||||||
|
} else {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function openTrace(string $path, int $type): void {
|
||||||
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||||
|
$trace = $this->unwindTrace($trace);
|
||||||
|
if (!isset($this->openTraces[$path])) {
|
||||||
|
$this->openTraces[$path] = [];
|
||||||
|
}
|
||||||
|
$this->openTraces[$path][] = [
|
||||||
|
'trace' => $trace,
|
||||||
|
'type' => $type,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the bits from the backtrace that are from this debug handling
|
||||||
|
*/
|
||||||
|
private function unwindTrace(array $backtrace): array {
|
||||||
|
while($backtrace[0]['class'] === self::class) {
|
||||||
|
array_shift($backtrace);
|
||||||
|
}
|
||||||
|
return $backtrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function closeTrace(string $path, int $type): void {
|
||||||
|
// todo find a way to better link acquire/release pairs
|
||||||
|
|
||||||
|
|
||||||
|
if (!isset($this->openTraces[$path])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->openTraces[$path] as $id => $trace) {
|
||||||
|
if ($trace['type'] === $type) {
|
||||||
|
unset($this->openTraces[$path][$id]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->openTraces[$path] = array_values($this->openTraces[$path]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function logConflict(string $path, int $type): void {
|
||||||
|
$path = $this->getPath($path);
|
||||||
|
|
||||||
|
// conflict comes from other request
|
||||||
|
// todo store traces in memcache so we can debug cross-requests conflicts
|
||||||
|
if (!isset($this->openTraces[$path])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->openTrace($path, $type);
|
||||||
|
|
||||||
|
$this->store->store($this->request->getId(), $this->openTraces[$path]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function acquireLock(string $path, int $type, ?string $readablePath = null): void {
|
||||||
|
try {
|
||||||
|
$this->inner->acquireLock($path, $type, $readablePath);
|
||||||
|
$this->openTrace($this->getPath($path, $readablePath), $type);
|
||||||
|
} catch (LockedException $e) {
|
||||||
|
$this->logConflict($path, $type);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changeLock(string $path, int $targetType): void {
|
||||||
|
try {
|
||||||
|
$this->inner->changeLock($path, $targetType);
|
||||||
|
$this->closeTrace($this->getPath($path), 3 - $targetType);
|
||||||
|
$this->openTrace($this->getPath($path), $targetType);
|
||||||
|
} catch (LockedException $e) {
|
||||||
|
$this->logConflict($path, $targetType);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function releaseLock(string $path, int $type): void {
|
||||||
|
$this->inner->releaseLock($path, $type);
|
||||||
|
$this->closeTrace($this->getPath($path), $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function releaseAll(): void {
|
||||||
|
$this->inner->releaseAll();
|
||||||
|
$this->openTraces = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLocked(string $path, int $type): bool {
|
||||||
|
return $this->inner->isLocked($path, $type);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
lib/LockTrace.php
Normal file
48
lib/LockTrace.php
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\LockPick;
|
||||||
|
|
||||||
|
use OCP\Lock\ILockingProvider;
|
||||||
|
|
||||||
|
class LockTrace {
|
||||||
|
private int $type;
|
||||||
|
private array $backtrace;
|
||||||
|
|
||||||
|
public function __construct(int $type, array $backtrace) {
|
||||||
|
$this->type = $type;
|
||||||
|
$this->backtrace = $backtrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): int {
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypeName(): string {
|
||||||
|
return $this->getType() === ILockingProvider::LOCK_SHARED ? 'shared' : 'exclusive';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBacktrace(): array {
|
||||||
|
return $this->backtrace;
|
||||||
|
}
|
||||||
|
}
|
||||||
64
lib/Migration/Version1Date20220803141943.php
Normal file
64
lib/Migration/Version1Date20220803141943.php
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Your name <your@email.com>
|
||||||
|
*
|
||||||
|
* @author Your name <your@email.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\LockPick\Migration;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use OC\DB\SchemaWrapper;
|
||||||
|
use OCP\DB\ISchemaWrapper;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\SimpleMigrationStep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated migration step: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
class Version1Date20220803141943 extends SimpleMigrationStep {
|
||||||
|
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||||
|
/** @var SchemaWrapper $schema */
|
||||||
|
$schema = $schemaClosure();
|
||||||
|
|
||||||
|
if (!$schema->hasTable("lockpick_traces")) {
|
||||||
|
$table = $schema->createTable("lockpick_traces");
|
||||||
|
$table->addColumn('trace_id', Types::BIGINT, [
|
||||||
|
'autoincrement' => true,
|
||||||
|
'notnull' => true,
|
||||||
|
'length' => 20,
|
||||||
|
]);
|
||||||
|
$table->addColumn('request_id', 'string', [
|
||||||
|
'notnull' => true,
|
||||||
|
'length' => 255,
|
||||||
|
]);
|
||||||
|
$table->addColumn('trace', 'text', [
|
||||||
|
'notnull' => true,
|
||||||
|
]);
|
||||||
|
$table->setPrimaryKey(['trace_id']);
|
||||||
|
$table->addIndex(['request_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
}
|
||||||
104
lib/TraceStore.php
Normal file
104
lib/TraceStore.php
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\LockPick;
|
||||||
|
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
class TraceStore {
|
||||||
|
private IDBConnection $connection;
|
||||||
|
|
||||||
|
public function __construct(IDBConnection $connection) {
|
||||||
|
$this->connection = $connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(string $request, array $trace): void {
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->insert('lockpick_traces')
|
||||||
|
->values([
|
||||||
|
'request_id' => $query->createNamedParameter($request),
|
||||||
|
'trace' => $query->createNamedParameter(json_encode($trace))
|
||||||
|
]);
|
||||||
|
$query->executeStatement();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return LockTrace[]|null
|
||||||
|
* @throws \OCP\DB\Exception
|
||||||
|
*/
|
||||||
|
public function last(): ?array {
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->select('trace')
|
||||||
|
->from('lockpick_traces')
|
||||||
|
->orderBy('trace_id', 'DESC')
|
||||||
|
->setMaxResults(1);
|
||||||
|
$trace = $query->executeQuery()->fetchOne();
|
||||||
|
if ($trace) {
|
||||||
|
$raw = json_decode($trace, true);
|
||||||
|
return array_map(function(array $item) {
|
||||||
|
return new LockTrace($item['type'], $item['trace']);
|
||||||
|
}, $raw);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function all(): array {
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->select('trace_id', 'request_id')
|
||||||
|
->from('lockpick_traces')
|
||||||
|
->orderBy('trace_id', 'ASC');
|
||||||
|
$rows = $query->executeQuery()->fetchAll();
|
||||||
|
$keys = array_map(function(array $row) {
|
||||||
|
return $row['trace_id'];
|
||||||
|
}, $rows);
|
||||||
|
$values = array_map(function(array $row) {
|
||||||
|
return $row['request_id'];
|
||||||
|
}, $rows);
|
||||||
|
return array_combine($keys, $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return LockTrace[]|null
|
||||||
|
* @throws \OCP\DB\Exception
|
||||||
|
*/
|
||||||
|
public function get(int $id): ?array {
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->select('trace')
|
||||||
|
->from('lockpick_traces')
|
||||||
|
->where($query->expr()->eq('trace_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||||
|
$trace = $query->executeQuery()->fetchOne();
|
||||||
|
if ($trace) {
|
||||||
|
$raw = json_decode($trace, true);
|
||||||
|
return array_map(function(array $item) {
|
||||||
|
return new LockTrace($item['type'], $item['trace']);
|
||||||
|
}, $raw);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue