sync with tomboy works
authorChristian Weiske <cweiske@cweiske.de>
Wed, 20 Aug 2014 16:49:03 +0000 (18:49 +0200)
committerChristian Weiske <cweiske@cweiske.de>
Wed, 20 Aug 2014 16:49:03 +0000 (18:49 +0200)
controller/apicontroller.php
lib/notestorage.php [new file with mode: 0644]

index 166436e..7f6bc67 100644 (file)
@@ -19,6 +19,7 @@ use \OCP\AppFramework\Http\JSONResponse;
 use \OCA\Grauphel\Lib\NoteStorage;
 use \OCA\Grauphel\Lib\OAuth;
 use \OCA\Grauphel\Lib\Dependencies;
 use \OCA\Grauphel\Lib\NoteStorage;
 use \OCA\Grauphel\Lib\OAuth;
 use \OCA\Grauphel\Lib\Dependencies;
+use \OCA\Grauphel\Lib\Response\ErrorResponse;
 
 /**
  * Tomboy's REST API
 
 /**
  * Tomboy's REST API
@@ -79,7 +80,7 @@ class ApiController extends Controller
             $username = $token->user;
 
         } catch (\OAuth_Exception $e) {
             $username = $token->user;
 
         } catch (\OAuth_Exception $e) {
-            $deps->renderer->errorOut($e->getMessage());
+            return new ErrorResponse($e->getMessage());
         } catch (\OAuthException $e) {
             if ($e->getCode() != OAUTH_PARAMETER_ABSENT) {
                 $oauth->error($e);
         } catch (\OAuthException $e) {
             if ($e->getCode() != OAUTH_PARAMETER_ABSENT) {
                 $oauth->error($e);
@@ -138,7 +139,14 @@ class ApiController extends Controller
      */
     public function user($username)
     {
      */
     public function user($username)
     {
-        $this->verifyUser($username);
+        $this->verifyUser(
+            $username,
+            $this->deps->urlGen->getAbsoluteURL(
+                $this->deps->urlGen->linkToRoute(
+                    'grauphel.api.user', array('username' => $username)
+                )
+            )
+        );
         $syncdata = $this->notes->loadSyncData($username);
 
         $data = array(
         $syncdata = $this->notes->loadSyncData($username);
 
         $data = array(
@@ -193,13 +201,16 @@ class ApiController extends Controller
             $username,
             $this->deps->urlGen->getAbsoluteURL(
                 $this->deps->urlGen->linkToRoute(
             $username,
             $this->deps->urlGen->getAbsoluteURL(
                 $this->deps->urlGen->linkToRoute(
-                    'grauphel.api.user', array('username' => $username)
+                    'grauphel.api.notesSave', array('username' => $username)
                 )
             )
         );
         $syncdata = $this->notes->loadSyncData($username);
         
                 )
             )
         );
         $syncdata = $this->notes->loadSyncData($username);
         
-        $this->handleNoteSave($username, $syncdata);
+        $res = $this->handleNoteSave($username, $syncdata);
+        if ($res instanceof \OCP\AppFramework\Http\Response) {
+            return $res;
+        }
 
         return $this->fetchNotes($username, $syncdata);
     }
 
         return $this->fetchNotes($username, $syncdata);
     }
@@ -237,48 +248,52 @@ class ApiController extends Controller
             return;
         }
 
             return;
         }
 
-        $data = file_get_contents('php://input');
-        $putObj = json_decode($data);
-        if ($putObj === NULL) {
-            errorOut('Invalid JSON data in PUT request');
-        }
+        //note that we have more data in $arPut than just our JSON
+        // request object merges it with other data
+        $arPut = $this->request->put;
 
         //structural validation
 
         //structural validation
-        if (!isset($putObj->{'latest-sync-revision'})) {
-            errorOut('Missing "latest-sync-revision"');
+        if (!isset($arPut['latest-sync-revision'])) {
+            return new ErrorResponse('Missing "latest-sync-revision"');
         }
         }
-        if (!isset($putObj->{'note-changes'})) {
-            errorOut('Missing "note-changes"');
+        if (!isset($arPut['note-changes'])) {
+            return new ErrorResponse('Missing "note-changes"');
         }
         }
-        foreach ($putObj->{'note-changes'} as $note) {
+        foreach ($arPut['note-changes'] as $note) {
+            //owncloud converts object to array, so we reverse
+            $note = (object) $note;
             if (!isset($note->guid) || $note->guid == '') {
             if (!isset($note->guid) || $note->guid == '') {
-                errorOut('Missing "guid" on note');
+                return new ErrorResponse('Missing "guid" on note');
             }
         }
 
         //content validation
             }
         }
 
         //content validation
-        if ($putObj->{'latest-sync-revision'} != $syncdata->latestSyncRevision +1
+        if ($arPut['latest-sync-revision'] != $syncdata->latestSyncRevision +1
             && $syncdata->latestSyncRevision != -1
         ) {
             && $syncdata->latestSyncRevision != -1
         ) {
-            errorOut('Wrong "latest-sync-revision". You are not up to date.');
+            return new ErrorResponse(
+                'Wrong "latest-sync-revision". You are not up to date.'
+            );
         }
 
         //update
         }
 
         //update
-        $deps = Dependencies::get();
         ++$syncdata->latestSyncRevision;
         ++$syncdata->latestSyncRevision;
-        foreach ($putObj->{'note-changes'} as $noteUpdate) {
-            $note = $deps->notes->load($username, $noteUpdate->guid);
+        foreach ($arPut['note-changes'] as $noteUpdate) {
+            //owncloud converts object to array, so we reverse
+            $noteUpdate = (object) $noteUpdate;
+
+            $note = $this->notes->load($username, $noteUpdate->guid);
             if (isset($noteUpdate->command) && $noteUpdate->command == 'delete') {
             if (isset($noteUpdate->command) && $noteUpdate->command == 'delete') {
-                $deps->notes->delete($username, $noteUpdate->guid);
+                $this->notes->delete($username, $noteUpdate->guid);
             } else {
             } else {
-                $deps->notes->update(
+                $this->notes->update(
                     $note, $noteUpdate, $syncdata->latestSyncRevision
                 );
                     $note, $noteUpdate, $syncdata->latestSyncRevision
                 );
-                $deps->notes->save($username, $note);
+                $this->notes->save($username, $note);
             }
         }
 
             }
         }
 
-        $deps->notes->saveSyncData($username, $syncdata);
+        $this->notes->saveSyncData($username, $syncdata);
     }
 
     /**
     }
 
     /**
@@ -298,7 +313,7 @@ class ApiController extends Controller
         $oauth->setDeps($deps);
         $oauth->verifyOAuthUser($username, $deps->urlGen->note($username, $guid));
 
         $oauth->setDeps($deps);
         $oauth->verifyOAuthUser($username, $deps->urlGen->note($username, $guid));
 
-        $note = $deps->notes->load($username, $guid, false);
+        $note = $this->notes->load($username, $guid, false);
         if ($note === null) {
             header('HTTP/1.0 404 Not Found');
             header('Content-type: text/plain');
         if ($note === null) {
             header('HTTP/1.0 404 Not Found');
             header('Content-type: text/plain');
diff --git a/lib/notestorage.php b/lib/notestorage.php
new file mode 100644 (file)
index 0000000..121f5cb
--- /dev/null
@@ -0,0 +1,383 @@
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link      http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Lib;
+
+/**
+ * Flat file storage for notes
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version   Release: @package_version@
+ * @link      http://cweiske.de/grauphel.htm
+ */
+class NoteStorage
+{
+    protected $urlGen;
+
+    public function __construct($urlGen)
+    {
+        $this->urlGen = $urlGen;
+    }
+    /**
+     * Create a new sync data object for fresh users.
+     * Used by loadSyncData()
+     *
+     * @param string $username User name
+     *
+     * @return SyncData New synchronization statistics
+     */
+    protected function getNewSyncData($username)
+    {
+        $syncdata = new SyncData();
+        $syncdata->initNew($username);
+        return $syncdata;
+    }
+
+    /**
+     * Updates the given $note object with data from $noteUpdate.
+     * Sets the last-sync-revision to $syncRevision
+     *
+     * @param object  $note         Original note object
+     * @param object  $noteUpdate   Update note object from a PUT to the API
+     * @param integer $syncRevision Current sync revision number
+     *
+     * @return void
+     */
+    public function update($note, $noteUpdate, $syncRevision)
+    {
+        static $updateFields = array(
+            'create-date',
+            'last-change-date',
+            'last-metadata-change-date',
+            'note-content',
+            'note-content-version',
+            'open-on-startup',
+            'pinned',
+            'tags',
+            'title',
+        );
+
+        $changed = array();
+        foreach ($updateFields as $field) {
+            $changed[$field] = false;
+            if (isset($noteUpdate->$field)) {
+                if ($note->$field != $noteUpdate->$field) {
+                    $note->$field = $noteUpdate->$field;
+                    $changed[$field] = true;
+                }
+            }
+        }
+
+        if (!isset($noteUpdate->{'last-change-date'})
+            && ($changed['title'] || $changed['note-content'])
+        ) {
+            //no idea how to get the microseconds in there
+            $note->{'last-change-date'} = date('c');
+        }
+
+        if (!isset($noteUpdate->{'last-metadata-change-date'})) {
+            //no idea how to get the microseconds in there
+            $note->{'last-metadata-change-date'} = date('c');
+        }
+
+        if (isset($noteUpdate->{'node-content'})
+            && $note->{'note-content-version'} == 0
+        ) {
+            $note->{'note-content-version'} = 0.3;
+        }
+
+        $note->{'last-sync-revision'} = $syncRevision;
+    }
+
+    /**
+     * Loads synchronization data for the given user.
+     * Creates fresh sync data if there are none for the user.
+     *
+     * @param string $username User name
+     *
+     * @return SyncData Synchronization statistics (revision, sync guid)
+     */
+    public function loadSyncData($username)
+    {
+        $row = \OC_DB::executeAudited(
+            'SELECT * FROM `*PREFIX*grauphel_syncdata`'
+            . ' WHERE `syncdata_user` = ?',
+            array($username)
+        )->fetchRow();
+
+        if ($row === false) {
+            $syncdata = $this->getNewSyncData($username);
+            $this->saveSyncData($username, $syncdata);
+        } else {
+            $syncdata = new SyncData();
+            $syncdata->latestSyncRevision = (int) $row['syncdata_latest_sync_revision'];
+            $syncdata->currentSyncGuid    = $row['syncdata_current_sync_guid'];
+        }
+
+        return $syncdata;
+    }
+
+    /**
+     * Save synchronization data for the given user.
+     *
+     * @param string   $username User name
+     * @param SyncData $syncdata Synchronization data object
+     *
+     * @return void
+     */
+    public function saveSyncData($username, SyncData $syncdata)
+    {
+        $row = \OC_DB::executeAudited(
+            'SELECT * FROM `*PREFIX*grauphel_syncdata`'
+            . ' WHERE `syncdata_user` = ?',
+            array($username)
+        )->fetchRow();
+
+        if ($row === false) {
+            //INSERT
+            $sql = 'INSERT INTO `*PREFIX*grauphel_syncdata`'
+                . '(`syncdata_user`, `syncdata_latest_sync_revision`, `syncdata_current_sync_guid`)'
+                . ' VALUES(?, ?, ?)';
+            $params = array(
+                $username,
+                $syncdata->latestSyncRevision,
+                $syncdata->currentSyncGuid
+            );
+        } else {
+            //UPDATE
+            $data = array(
+                'syncdata_latest_sync_revision' => $syncdata->latestSyncRevision,
+                'syncdata_current_sync_guid'    => $syncdata->currentSyncGuid,
+            );
+            $sql = 'UPDATE `*PREFIX*grauphel_syncdata` SET'
+                . ' `' . implode('` = ?, `', array_keys($data)) . '` = ?'
+                . ' WHERE `syncdata_user` = ?';
+            $params = array_values($data);
+            $params[] = $username;
+        }
+        \OC_DB::executeAudited($sql, $params);
+    }
+
+    /**
+     * Load a note from the storage.
+     *
+     * @param string  $username  User name
+     * @param string  $guid      Note identifier
+     * @param boolean $createNew Create a new note if it does not exist
+     *
+     * @return object Note object, NULL if !$createNew and note does not exist
+     */
+    public function load($username, $guid, $createNew = true)
+    {
+        $row = \OC_DB::executeAudited(
+            'SELECT * FROM `*PREFIX*grauphel_notes`'
+            . ' WHERE `note_user` = ? AND `note_guid` = ?',
+            array($username, $guid)
+        )->fetchRow();
+
+        if ($row === false) {
+            if (!$createNew) {
+                return null;
+            }
+            return (object) array(
+                'guid' => $guid,
+
+                'create-date'               => null,
+                'last-change-date'          => null,
+                'last-metadata-change-date' => null,
+
+                'title'                => null,
+                'note-content'         => null,
+                'note-content-version' => 0.3,
+
+                'open-on-startup' => false,
+                'pinned'          => false,
+                'tags'            => array(),
+            );
+        }
+        
+        return $this->noteFromRow($row);
+    }
+
+    /**
+     * Save a note into storage.
+     *
+     * @param string $username User name
+     * @param object $note     Note to save
+     *
+     * @return void
+     */
+    public function save($username, $note)
+    {
+        $row = \OC_DB::executeAudited(
+            'SELECT * FROM `*PREFIX*grauphel_notes`'
+            . ' WHERE `note_user` = ? AND `note_guid` = ?',
+            array($username, $note->guid)
+        )->fetchRow();
+
+        $data = $this->rowFromNote($note);
+        if ($row === false) {
+            //INSERT
+            $data['note_user'] = $username;
+            $sql = 'INSERT INTO `*PREFIX*grauphel_notes`'
+                . ' (`' . implode('`, `', array_keys($data)) . '`)'
+                . ' VALUES(' . implode(', ', array_fill(0, count($data), '?')) . ')';
+            $params = array_values($data);
+        } else {
+            //UPDATE
+            $sql = 'UPDATE `*PREFIX*grauphel_notes` SET '
+                . '`' . implode('` = ?, `', array_keys($data)) . '` = ?'
+                . ' WHERE `note_user` = ? AND `note_guid` = ?';
+            $params = array_values($data);
+            $params[] = $username;
+            $params[] = $note->guid;
+        }
+        \OC_DB::executeAudited($sql, $params);
+    }
+
+    /**
+     * Delete a note from storage.
+     *
+     * @param string $username User name
+     * @param object $guid     ID of the note
+     *
+     * @return void
+     */
+    public function delete($username, $guid)
+    {
+        \OC_DB::executeAudited(
+            'DELETE FROM `*PREFIX*grauphel_notes`'
+            . ' WHERE `note_user` = ? AND `note_guid` = ?',
+                       array($username, $guid)
+               );
+    }
+
+    /**
+     * Load notes for the given user in short form.
+     * Optionally only those changed after $since revision
+     *
+     * @param string  $username User name
+     * @param integer $since    Revision number after which the notes changed
+     *
+     * @return array Array of short note objects
+     */
+    public function loadNotesOverview($username, $since = null)
+    {
+               $result = \OC_DB::executeAudited(
+            'SELECT `note_guid`, `note_title`, `note_last_sync_revision`'
+            . ' FROM `*PREFIX*grauphel_notes`'
+            . ' WHERE note_user = ?',
+                       array($username)
+               );
+
+               $notes = array();
+               while ($row = $result->fetchRow()) {
+            if ($since !== null && $row['note_last_sync_revision'] <= $since) {
+                continue;
+            }
+            $notes[] = array(
+                'guid' => $row['note_guid'],
+                'ref'  => array(
+                    'api-ref' => $this->urlGen->getAbsoluteURL(
+                        $this->urlGen->linkToRoute(
+                            'grauphel.api.note',
+                            array(
+                                'username' => $username,
+                                'guid' => $row['note_guid']
+                            )
+                        )
+                    ),
+                    'href' => null,//FIXME
+                ),
+                'title' => $row['note_title'],
+            );
+               }
+
+        return $notes;
+    }
+
+    /**
+     * Load notes for the given user in full form.
+     * Optionally only those changed after $since revision
+     *
+     * @param string  $username User name
+     * @param integer $since    Revision number after which the notes changed
+     *
+     * @return array Array of full note objects
+     */
+    public function loadNotesFull($username, $since = null)
+    {
+               $result = \OC_DB::executeAudited(
+            'SELECT * FROM `*PREFIX*grauphel_notes`'
+            . ' WHERE note_user = ?',
+                       array($username)
+               );
+
+               $notes = array();
+               while ($row = $result->fetchRow()) {
+            if ($since !== null && $row['note_last_sync_revision'] <= $since) {
+                continue;
+            }
+            $notes[] = $this->noteFromRow($row);
+               }
+
+        return $notes;
+    }
+
+    protected function noteFromRow($row)
+    {
+        return (object) array(
+            'guid'  => $row['note_guid'],
+
+            'create-date'               => $row['note_create_date'],
+            'last-change-date'          => $row['note_last_change_date'],
+            'last-metadata-change-date' => $row['note_last_metadata_change_date'],
+
+            'title'                => $row['note_title'],
+            'note-content'         => $row['note_content'],
+            'note-content-version' => $row['note_content_version'],
+
+            'open-on-startup' => $row['note_open_on_startup'],
+            'pinned'          => $row['note_pinned'],
+            'tags'            => json_decode($row['note_tags']),
+
+            'last-sync-revision' => $row['note_last_sync_revision'],
+        );
+    }
+
+    protected function rowFromNote($note)
+    {
+        return array(
+            'note_guid'  => $note->guid,
+            'note_title' => $note->title,
+
+            'note_content'         => $note->{'note-content'},
+            'note_content_version' => $note->{'note-content-version'},
+
+            'note_create_date'               => $note->{'create-date'},
+            'note_last_change_date'          => $note->{'last-change-date'},
+            'note_last_metadata_change_date' => $note->{'last-metadata-change-date'},
+            
+            'note_open_on_startup' => $note->{'open-on-startup'},
+            'note_pinned'          => $note->pinned,
+            'note_tags'            => json_encode($note->tags),
+
+            'note_last_sync_revision' => $note->{'last-sync-revision'},
+        );
+    }
+}
+?>
\ No newline at end of file