// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.export.sheet;
import static com.google.common.base.Strings.nullToEmpty;
import com.google.api.services.sheets.v4.Sheets;
import com.google.api.services.sheets.v4.model.AppendValuesResponse;
import com.google.api.services.sheets.v4.model.BatchUpdateValuesRequest;
import com.google.api.services.sheets.v4.model.BatchUpdateValuesResponse;
import com.google.api.services.sheets.v4.model.ClearValuesRequest;
import com.google.api.services.sheets.v4.model.ClearValuesResponse;
import com.google.api.services.sheets.v4.model.ValueRange;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
/** Generic data synchronization utility for Google Spreadsheets. */
class SheetSynchronizer {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String SHEET_NAME = "Registrars";
@Inject Sheets sheetsService;
@Inject SheetSynchronizer() {}
/**
* Replace the contents of a Google Spreadsheet with {@code data}.
*
*
In order for this to work, you must create a spreadsheet with a header row, each containing
* the column name, without any spaces. All subsequent rows are considered data, so long as
* they're not blank. If you have a blank row in the middle of your data, you're going to have
* problems. You must also make sure that the spreadsheet has been shared with the API client
* credential email address.
*
*
The algorithm works by first assuming that the spreadsheet is sorted in the same way that
* {@code data} is sorted (i.e. registrar name order). It then iterates through the existing rows
* and comparing them to the items in {@code data}. Iteration continues until we either run out of
* rows, or items in {@code data}. If there's any rows remaining, they'll be deleted. If instead,
* items remain in data, they'll be appended to the end of the sheet.
*
* @param spreadsheetId The ID of your spreadsheet. This can be obtained by opening the Google
* spreadsheet in your browser and copying the ID from the URL.
* @param data This should be a sorted list of rows containing the entirety of the
* spreadsheet. Each row is a map, where the key must be exactly the same as the column header
* cell in the spreadsheet, and value is an arbitrary object which will be converted to a
* string before storing it in the spreadsheet.
* @throws IOException if encountering an error communicating with the Sheets service.
* @see Google Sheets API v4
*/
void synchronize(String spreadsheetId, ImmutableList> data)
throws IOException {
// Get the existing sheet's values
ValueRange sheetValues =
sheetsService.spreadsheets().values().get(spreadsheetId, SHEET_NAME).execute();
List> originalVals = sheetValues.getValues();
// Assemble headers from the sheet
ImmutableList.Builder headersBuilder = new ImmutableList.Builder<>();
for (Object headerCell : originalVals.get(0)) {
headersBuilder.add(headerCell.toString());
}
ImmutableList headers = headersBuilder.build();
// Pop off the headers row
originalVals.remove(0);
List updates = new ArrayList<>();
int minSize = Math.min(originalVals.size(), data.size());
for (int i = 0; i < minSize; i++) {
boolean mutated = false;
List