Merge pull request #95 from nextcloud/api-speed-up

API speed-up (ETag, Last-Modified)
This commit is contained in:
Hendrik Leppelsack 2017-06-27 15:53:34 +02:00 committed by GitHub
commit 6df9129567
7 changed files with 233 additions and 6 deletions

59
appinfo/database.xml Normal file
View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" ?>
<database>
<name>*dbname*</name>
<create>true</create>
<charset>utf8</charset>
<table>
<name>*dbprefix*notes_meta</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<notnull>true</notnull>
<autoincrement>true</autoincrement>
</field>
<field>
<name>file_id</name>
<type>integer</type>
<notnull>true</notnull>
</field>
<field>
<name>user_id</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>last_update</name>
<type>integer</type>
<notnull>true</notnull>
</field>
<field>
<name>etag</name>
<type>text</type>
<notnull>true</notnull>
<length>32</length>
</field>
<index>
<name>notes_meta_file_id_index</name>
<field>
<name>file_id</name>
</field>
</index>
<index>
<name>notes_meta_user_id_index</name>
<field>
<name>user_id</name>
</field>
</index>
<index>
<name>notes_meta_file_id_user_id_index</name>
<unique>true</unique>
<field>
<name>file_id</name>
<name>user_id</name>
</field>
</index>
</declaration>
</table>
</database>

View File

@ -12,10 +12,12 @@
namespace OCA\Notes\Controller;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
use OCA\Notes\Service\NotesService;
use OCA\Notes\Service\MetaService;
use OCA\Notes\Db\Note;
/**
@ -29,6 +31,8 @@ class NotesApiController extends ApiController {
/** @var NotesService */
private $service;
/** @var MetaService */
private $metaService;
/** @var string */
private $userId;
@ -38,10 +42,10 @@ class NotesApiController extends ApiController {
* @param NotesService $service
* @param string $UserId
*/
public function __construct($AppName, IRequest $request,
NotesService $service, $UserId){
public function __construct($AppName, IRequest $request, NotesService $service, MetaService $metaService, $UserId) {
parent::__construct($AppName, $request);
$this->service = $service;
$this->metaService = $metaService;
$this->userId = $UserId;
}
@ -52,7 +56,7 @@ class NotesApiController extends ApiController {
* notes
* @return Note
*/
private function excludeFields(Note $note, array $exclude) {
private function excludeFields(Note &$note, array $exclude) {
if(count($exclude) > 0) {
foreach ($exclude as $field) {
if(property_exists($note, $field)) {
@ -72,13 +76,28 @@ class NotesApiController extends ApiController {
* @param string $exclude
* @return DataResponse
*/
public function index($exclude='') {
public function index($exclude='', $pruneBefore=0) {
$exclude = explode(',', $exclude);
$now = new \DateTime(); // this must be before loading notes if there are concurrent changes possible
$notes = $this->service->getAll($this->userId);
$metas = $this->metaService->updateAll($this->userId, $notes);
foreach ($notes as $note) {
$note = $this->excludeFields($note, $exclude);
$lastUpdate = $metas[$note->getId()]->getLastUpdate();
if($pruneBefore && $lastUpdate<$pruneBefore) {
$vars = get_object_vars($note);
unset($vars['id']);
$this->excludeFields($note, array_keys($vars));
} else {
$this->excludeFields($note, $exclude);
}
}
return new DataResponse($notes);
$etag = md5(json_encode($notes));
if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') {
return new DataResponse([], Http::STATUS_NOT_MODIFIED);
}
return (new DataResponse($notes))
->setLastModified($now)
->setETag($etag);
}

32
db/meta.php Normal file
View File

@ -0,0 +1,32 @@
<?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
namespace OCA\Notes\Db;
use OCP\AppFramework\Db\Entity;
class Meta extends Entity {
public $userId;
public $fileId;
public $lastUpdate;
public $etag;
/**
* @param Note $note
* @return static
*/
public static function fromNote(Note $note, $userId) {
$meta = new static();
$meta->setUserId($userId);
$meta->setFileId($note->getId());
$meta->setLastUpdate(time());
$meta->setEtag($note->getEtag());
return $meta;
}
}

29
db/metamapper.php Normal file
View File

@ -0,0 +1,29 @@
<?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
namespace OCA\Notes\Db;
use OCP\IDBConnection;
use OCP\AppFramework\Db\Mapper;
class MetaMapper extends Mapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'notes_meta');
}
public function getAll($userId) {
$sql = 'SELECT * FROM `*PREFIX*notes_meta` WHERE user_id=?';
return $this->findEntities($sql, [$userId]);
}
public function get($userId, $fileId) {
$sql = 'SELECT * FROM `*PREFIX*notes_meta` WHERE user_id=? AND file_id=?';
return $this->findEntity($sql, [$userId, $fileId]);
}
}

View File

@ -19,6 +19,8 @@ use OCP\AppFramework\Db\Entity;
* Class Note
* @method integer getId()
* @method void setId(integer $value)
* @method string getEtag()
* @method void setEtag(string $value)
* @method integer getModified()
* @method void setModified(integer $value)
* @method string getTitle()
@ -33,6 +35,7 @@ use OCP\AppFramework\Db\Entity;
*/
class Note extends Entity {
public $etag;
public $modified;
public $title;
public $category;
@ -60,6 +63,7 @@ class Note extends Entity {
$note->setFavorite(true);
//unset($tags[array_search(\OC\Tags::TAG_FAVORITE, $tags)]);
}
$note->updateETag();
$note->resetUpdatedFields();
return $note;
}
@ -70,4 +74,16 @@ class Note extends Entity {
}
return $str;
}
private function updateETag() {
// collect all relevant attributes
$data = '';
foreach(get_object_vars($this) as $key => $val) {
if($key!=='etag') {
$data .= $val;
}
}
$etag = md5($data);
$this->setEtag($etag);
}
}

69
service/metaservice.php Normal file
View File

@ -0,0 +1,69 @@
<?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
namespace OCA\Notes\Service;
use OCA\Notes\Db\Note;
use OCA\Notes\Db\Meta;
use OCA\Notes\Db\MetaMapper;
/**
* Class MetaService
*
* @package OCA\Notes\Service
*/
class MetaService {
private $metaMapper;
public function __construct(MetaMapper $metaMapper) {
$this->metaMapper = $metaMapper;
}
public function updateAll($userId, Array $notes) {
$metas = $this->metaMapper->getAll($userId);
$metas = $this->getIndexedArray($metas, 'fileId');
$notes = $this->getIndexedArray($notes, 'id');
foreach($metas as $id=>$meta) {
if(!array_key_exists($id, $notes)) {
// DELETE obsolete notes
$this->metaMapper->delete($meta);
unset($metas[$id]);
}
}
foreach($notes as $id=>$note) {
if(!array_key_exists($id, $metas)) {
// INSERT new notes
$metas[$note->getId()] = $this->create($userId, $note);
} elseif($note->getEtag()!==$metas[$id]->getEtag()) {
// UPDATE changed notes
$meta = $metas[$id];
$this->updateIfNeeded($meta, $note);
}
}
return $metas;
}
private function getIndexedArray(array $data, $property) {
$property = ucfirst($property);
$getter = 'get'.$property;
$result = array();
foreach($data as $entity) {
$result[$entity->$getter()] = $entity;
}
return $result;
}
private function updateIfNeeded(&$meta, $note) {
if($note->getEtag()!==$meta->getEtag()) {
$meta->setEtag($note->getEtag());
$meta->setLastUpdate(time());
$this->metaMapper->update($meta);
}
}
}

View File

@ -87,12 +87,14 @@ class NotesApiControllerTest extends PHPUnit_Framework_TestCase {
$this->assertEquals(json_encode([
[
'etag' => null,
'modified' => 123,
'category' => null,
'favorite' => false,
'id' => 3,
],
[
'etag' => null,
'modified' => 111,
'category' => null,
'favorite' => false,
@ -139,6 +141,7 @@ class NotesApiControllerTest extends PHPUnit_Framework_TestCase {
$response = $this->controller->get(3, 'title,content');
$this->assertEquals(json_encode([
'etag' => null,
'modified' => 123,
'category' => null,
'favorite' => false,