Merge pull request #95 from nextcloud/api-speed-up
API speed-up (ETag, Last-Modified)
This commit is contained in:
commit
6df9129567
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
16
db/note.php
16
db/note.php
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue