Add support to set diff preferences via REST

The account diff preferences can now be set by PUT on
'/accounts/*/preferences.diff'.

Change-Id: I08e1af7cff4fbe3b17d1fa6a7a2af2763f5ecd0b
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 71edfec..f4769984 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -292,6 +292,58 @@
   }
 ----
 
+[[set-diff-preferences]]
+Set Diff Preferences
+~~~~~~~~~~~~~~~~~~~~
+[verse]
+'PUT /accounts/link:#account-id[\{account-id\}]/preferences.diff'
+
+Sets the diff preferences of a user.
+
+The new diff preferences must be provided in the request body as a
+link:#diff-preferences-input[DiffPreferencesInput] entity.
+
+.Request
+----
+  GET /a/accounts/self/preferences.diff HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "context": 10,
+    "ignore_whitespace": "IGNORE_ALL_SPACE",
+    "intraline_difference": true,
+    "line_length": 100,
+    "show_line_endings": true,
+    "show_tabs": true,
+    "show_whitespace_errors": true,
+    "syntax_highlighting": true,
+    "tab_size": 8
+  }
+----
+
+As result the new diff preferences of the user are returned as a
+link:#diff-preferences-info[DiffPreferencesInfo] entity.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "context": 10,
+    "ignore_whitespace": "IGNORE_ALL_SPACE",
+    "intraline_difference": true,
+    "line_length": 100,
+    "show_line_endings": true,
+    "show_tabs": true,
+    "show_whitespace_errors": true,
+    "syntax_highlighting": true,
+    "tab_size": 8
+  }
+----
+
 
 [[ids]]
 IDs
@@ -430,6 +482,53 @@
 Number of spaces that should be used to display one tab.
 |=====================================
 
+[[diff-preferences-input]]
+DiffPreferencesInput
+~~~~~~~~~~~~~~~~~~~~
+The `DiffPreferencesInput` entity contains information for setting the
+diff preferences of a user. Fields which are not set will not be
+updated.
+
+[options="header",width="50%",cols="1,^1,5"]
+|=====================================
+|Field Name              ||Description
+|`context`               |optional|
+The number of lines of context when viewing a patch.
+|`expand_all_comments`   |optional|
+Whether all inline comments should be automatically expanded.
+|`ignore_whitespace`     |optional|
+Whether whitespace changes should be ignored and if yes, which
+whitespace changes should be ignored. +
+Allowed values are `IGNORE_NONE`, `IGNORE_SPACE_AT_EOL`,
+`IGNORE_SPACE_CHANGE`, `IGNORE_ALL_SPACE`.
+|`intraline_difference`  |optional|
+Whether intraline differences should be highlighted.
+|`line_length`           |optional|
+Number of characters that should be displayed in one line.
+|`manual_review`         |optional|
+Whether the 'Reviewed' flag should not be set automatically on a patch
+when it is viewed.
+|`retain_header`         |optional|
+Whether the header that is displayed above the patch (that either shows
+the commit message, the diff preferences, the patch sets or the files)
+should be retained on file switch.
+|`show_line_endings`     |optional|
+Whether Windows EOL/Cr-Lf should be displayed as '\r' in a dotted-line
+box.
+|`show_tabs`             |optional|
+Whether tabs should be shown.
+|`show_whitespace_errors`|optional|
+Whether whitespace errors should be shown.
+|`skip_deleted`          |optional|
+Whether deleted files should be skipped on file switch.
+|`skip_uncommented`      |optional|
+Whether uncommented files should be skipped on file switch.
+|`syntax_highlighting`   |optional|
+Whether syntax highlighting should be enabled.
+|`tab_size`              |optional|
+Number of spaces that should be used to display one tab.
+|=====================================
+
 [[query-limit-info]]
 QueryLimitInfo
 ~~~~~~~~~~~~~~
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index 17cf479..a8bd6e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -34,6 +34,7 @@
     child(ACCOUNT_KIND, "capabilities").to(Capabilities.class);
     get(ACCOUNT_KIND, "groups").to(GetGroups.class);
     get(ACCOUNT_KIND, "preferences.diff").to(GetDiffPreferences.class);
+    put(ACCOUNT_KIND, "preferences.diff").to(SetDiffPreferences.class);
     get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
new file mode 100644
index 0000000..db9bc2d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
@@ -0,0 +1,130 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.GetDiffPreferences.DiffPreferencesInfo;
+import com.google.gerrit.server.account.SetDiffPreferences.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.util.Collections;
+
+public class SetDiffPreferences implements RestModifyView<AccountResource, Input> {
+  static class Input {
+    Short context;
+    Boolean expandAllComments;
+    Whitespace ignoreWhitespace;
+    Boolean intralineDifference;
+    Integer lineLength;
+    Boolean manualReview;
+    Boolean retainHeader;
+    Boolean showLineEndings;
+    Boolean showTabs;
+    Boolean showWhitespaceErrors;
+    Boolean skipDeleted;
+    Boolean skipUncommented;
+    Boolean syntaxHighlighting;
+    Integer tabSize;
+  }
+
+  private final Provider<CurrentUser> self;
+  private final ReviewDb db;
+
+  @Inject
+  SetDiffPreferences(Provider<CurrentUser> self, ReviewDb db) {
+    this.self = self;
+    this.db = db;
+  }
+
+  @Override
+  public DiffPreferencesInfo apply(AccountResource rsrc, Input input)
+      throws AuthException, OrmException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("restricted to administrator");
+    }
+    if (input == null) {
+      input = new Input();
+    }
+
+    Account.Id accountId = rsrc.getUser().getAccountId();
+    AccountDiffPreference p;
+
+    db.accounts().beginTransaction(accountId);
+    try {
+      p = db.accountDiffPreferences().get(accountId);
+      if (p == null) {
+        p = new AccountDiffPreference(accountId);
+      }
+
+      if (input.context != null) {
+        p.setContext(input.context);
+      }
+      if (input.ignoreWhitespace != null) {
+        p.setIgnoreWhitespace(input.ignoreWhitespace);
+      }
+      if (input.expandAllComments != null) {
+        p.setExpandAllComments(input.expandAllComments);
+      }
+      if (input.intralineDifference != null) {
+        p.setIntralineDifference(input.intralineDifference);
+      }
+      if (input.lineLength != null) {
+        p.setLineLength(input.lineLength);
+      }
+      if (input.manualReview != null) {
+        p.setManualReview(input.manualReview);
+      }
+      if (input.retainHeader != null) {
+        p.setRetainHeader(input.retainHeader);
+      }
+      if (input.showLineEndings != null) {
+        p.setShowLineEndings(input.showLineEndings);
+      }
+      if (input.showTabs != null) {
+        p.setShowTabs(input.showTabs);
+      }
+      if (input.showWhitespaceErrors != null) {
+        p.setShowWhitespaceErrors(input.showWhitespaceErrors);
+      }
+      if (input.skipDeleted != null) {
+        p.setSkipDeleted(input.skipDeleted);
+      }
+      if (input.skipUncommented != null) {
+        p.setSkipUncommented(input.skipUncommented);
+      }
+      if (input.syntaxHighlighting != null) {
+        p.setSyntaxHighlighting(input.syntaxHighlighting);
+      }
+      if (input.tabSize != null) {
+        p.setTabSize(input.tabSize);
+      }
+
+      db.accountDiffPreferences().upsert(Collections.singleton(p));
+      db.commit();
+    } finally {
+      db.rollback();
+    }
+    return DiffPreferencesInfo.parse(p);
+  }
+}