nextcloud-notes/lib/Controller/Helper.php

164 lines
5.4 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\Notes\Controller;
use OCA\Notes\AppInfo\Application;
use OCA\Notes\Db\Meta;
use OCA\Notes\Service\Note;
use OCA\Notes\Service\NotesService;
use OCA\Notes\Service\MetaNote;
use OCA\Notes\Service\MetaService;
use OCA\Notes\Service\Util;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
class Helper {
private NotesService $notesService;
private MetaService $metaService;
public LoggerInterface $logger;
private IUserSession $userSession;
public function __construct(
NotesService $notesService,
MetaService $metaService,
IUserSession $userSession,
LoggerInterface $logger
) {
$this->notesService = $notesService;
$this->metaService = $metaService;
$this->userSession = $userSession;
$this->logger = $logger;
}
public function getUID() : string {
return $this->userSession->getUser()->getUID();
}
public function getNoteWithETagCheck(int $id, IRequest $request) : Note {
$userId = $this->getUID();
$note = $this->notesService->get($userId, $id);
$ifMatch = $request->getHeader('If-Match');
if ($ifMatch) {
$matchEtags = preg_split('/,\s*/', $ifMatch);
$meta = $this->metaService->update($userId, $note);
if (!in_array('"'.$meta->getEtag().'"', $matchEtags)) {
throw new ETagDoesNotMatchException($note);
}
}
return $note;
}
public function getNoteData(Note $note, array $exclude = [], Meta $meta = null) : array {
if ($meta === null) {
$meta = $this->metaService->update($this->getUID(), $note);
}
$data = $note->getData($exclude);
$data['etag'] = $meta->getEtag();
return $data;
}
public function getNotesAndCategories(
int $pruneBefore,
array $exclude,
string $category = null,
int $chunkSize = 0,
string $chunkCursorStr = null
) : array {
$userId = $this->getUID();
$chunkCursor = $chunkCursorStr ? ChunkCursor::fromString($chunkCursorStr) : null;
$lastUpdate = $chunkCursor->timeStart ?? new \DateTime();
$data = $this->notesService->getAll($userId, true); // auto-create notes folder if not exists
$metaNotes = $this->metaService->getAll($userId, $data['notes']);
// if a category is requested, then ignore all other notes
if ($category !== null) {
$metaNotes = array_filter($metaNotes, function (MetaNote $m) use ($category) {
return $m->note->getCategory() === $category;
});
}
// list of notes that should be sent to the client
$fullNotes = array_filter($metaNotes, function (MetaNote $m) use ($pruneBefore, $chunkCursor) {
$isPruned = $pruneBefore && $m->meta->getLastUpdate() < $pruneBefore;
$noteLastUpdate = (int)$m->meta->getLastUpdate();
$isBeforeCursor = $chunkCursor && (
$noteLastUpdate < $chunkCursor->noteLastUpdate
|| ($noteLastUpdate === $chunkCursor->noteLastUpdate
&& $m->note->getId() <= $chunkCursor->noteId)
);
return !$isPruned && !$isBeforeCursor;
});
// sort the list for slicing the next chunk
uasort($fullNotes, function (MetaNote $a, MetaNote $b) {
return $a->meta->getLastUpdate() <=> $b->meta->getLastUpdate()
?: $a->note->getId() <=> $b->note->getId();
});
// slice the next chunk
$chunkedNotes = $chunkSize ? array_slice($fullNotes, 0, $chunkSize, true) : $fullNotes;
$numPendingNotes = count($fullNotes) - count($chunkedNotes);
// if the chunk does not contain all remaining notes, then generate new chunk cursor
$newChunkCursor = $numPendingNotes ? ChunkCursor::fromNote($lastUpdate, end($chunkedNotes)) : null;
// load data for the current chunk
$notesData = array_map(function (MetaNote $m) use ($exclude) {
return $this->getNoteData($m->note, $exclude, $m->meta);
}, $chunkedNotes);
return [
'categories' => $data['categories'],
'notesAll' => $metaNotes,
'notesData' => $notesData,
'lastUpdate' => $lastUpdate,
'chunkCursor' => $newChunkCursor,
'numPendingNotes' => $numPendingNotes,
];
}
public function logException(\Throwable $e) : void {
$this->logger->error('Controller failed with '.get_class($e), [ 'exception' => $e ]);
}
public function createErrorResponse(\Throwable $e, int $statusCode) : JSONResponse {
$response = [
'errorType' => get_class($e)
];
return new JSONResponse($response, $statusCode);
}
public function handleErrorResponse(callable $respond) : JSONResponse {
try {
$data = Util::retryIfLocked($respond);
$response = $data instanceof JSONResponse ? $data : new JSONResponse($data);
} catch (\OCA\Notes\Controller\ETagDoesNotMatchException $e) {
$response = new JSONResponse($this->getNoteData($e->note), Http::STATUS_PRECONDITION_FAILED);
} catch (\OCA\Notes\Service\NoteDoesNotExistException $e) {
$this->logException($e);
$response = $this->createErrorResponse($e, Http::STATUS_NOT_FOUND);
} catch (\OCA\Notes\Service\InsufficientStorageException $e) {
$this->logException($e);
$response = $this->createErrorResponse($e, Http::STATUS_INSUFFICIENT_STORAGE);
} catch (\OCA\Notes\Service\NoteNotWritableException $e) {
$this->logException($e);
$response = $this->createErrorResponse($e, Http::STATUS_FORBIDDEN);
} catch (\OCP\Lock\LockedException $e) {
$this->logException($e);
$response = $this->createErrorResponse($e, Http::STATUS_LOCKED);
} catch (\Throwable $e) {
$this->logException($e);
$response = $this->createErrorResponse($e, Http::STATUS_INTERNAL_SERVER_ERROR);
}
$response->addHeader('X-Notes-API-Versions', implode(', ', Application::$API_VERSIONS));
return $response;
}
}