// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Sheets.v4;
using Google.Apis.Sheets.v4.Data;
using System.Threading;
using Google.Apis.Util.Store;
using Google.Apis.Services;
namespace SheetsHelper
{
public enum ESheetsDimension
{
ROWS,
COLUMNS
}
public enum ESheetsValueInputOption
{
RAW,
USER_ENTERED
}
public class SheetsUtils
{
private static DateTime QuotaTimeStart = new DateTime(0);
private static int QuotaRequestCount = 0;
private static int MAX_QUERY_COUNT = 100;
private static int MAX_QUERY_SECONDS = 100;
// Synchronous method that should be called before Any Google Sheets Query
// to ensure that a given test instance doesn't push over the limit by itself
public static void TryCheckQuotaSync()
{
DateTime RequestStart = DateTime.Now;
TimeSpan DiffTime = RequestStart - QuotaTimeStart;
QuotaRequestCount++;
if (DiffTime.TotalSeconds > MAX_QUERY_SECONDS)
{
// it's been long enough, so we just restart our time-counter
QuotaTimeStart = RequestStart;
QuotaRequestCount = 1;
}
else
{
if (QuotaRequestCount > MAX_QUERY_COUNT)
{
// Sleep the remaining time plus one second for safety
int RemainingMilliseconds = (101 * 1000) - (int)DiffTime.TotalMilliseconds;
Console.Write(string.Format("Waiting on Google Sheets quota for {0} milliseconds...", RemainingMilliseconds));
Thread.Sleep(RemainingMilliseconds);
Console.WriteLine("Complete!");
QuotaRequestCount -= MAX_QUERY_COUNT;
}
}
Console.WriteLine(string.Format("RequestCount: {0}, QuotaTime: {1}", QuotaRequestCount, (DateTime.Now - QuotaTimeStart).TotalSeconds));
}
///
/// Converts a zero-based index into a string in A1 notation. Example: ColumnIndex = 27 should return "AB".
///
public static string ColumnIndexToA1(int ColumnIndex)
{
string A1String = "";
int Base = 26;
int RemainingValue = ColumnIndex;
while (RemainingValue >= 0)
{
int RemainderDigit = RemainingValue % Base;
A1String = (char)(RemainderDigit + 65) + A1String; // (char)65 = 'A'
RemainingValue = (RemainingValue / Base) - 1;
}
return A1String;
}
}
public class SpreadSheetWrapper
{
protected Spreadsheet SourceSpreadsheet;
protected SheetsService Service;
protected string SpreadsheetID;
protected BatchUpdateSpreadsheetRequest UpdateSpreadsheetBatchRequest;
protected BatchUpdateValuesRequest UpdateValuesBatchRequest;
public SpreadSheetWrapper(string AppTitle, string SecretKeyPath, string CredentialPath, string InSpreadsheetID)
{
string[] Scopes = { SheetsService.Scope.Spreadsheets };
// Catch any exceptions to report both outer and inner as the Google stuff has a dependency on old Newtonsoft
// JSON that has been addressed via a rebinding, but has a habit of coming back and is tricky to solve.
try
{
UserCredential credential;
using (var stream =
new FileStream(SecretKeyPath, FileMode.Open, FileAccess.Read))
{
string credPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(Environment.CurrentDirectory, "Engine/Restricted/NotForLicensees/Source/Programs/AutomationTool/Gauntlet/Sheets");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(CredentialPath, true)).Result;
}
// Create Google Sheets API service.
Service = new SheetsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = AppTitle,
});
}
catch (System.Exception ex)
{
Console.Write("Error: Failed to create Sheet.\r\n\r\nException={0}\r\n\r\nInnerException={1}", ex, ex.InnerException);
throw ex.InnerException;
}
// Define request parameters.
SpreadsheetID = InSpreadsheetID;
Refresh();
UpdateSpreadsheetBatchRequest = new BatchUpdateSpreadsheetRequest();
UpdateValuesBatchRequest = new BatchUpdateValuesRequest();
}
public void Refresh()
{
SheetsUtils.TryCheckQuotaSync();
SourceSpreadsheet = Service.Spreadsheets.Get(SpreadsheetID).Execute();
}
// Gets an existing Sheet by its name or adds a new Sheet with the given name
public SheetWrapper GetSheet(string Title)
{
SheetWrapper ResultSheet = GetSheetByName(Title);
if (ResultSheet == null)
{
ResultSheet = AddSheet(Title);
}
return ResultSheet;
}
// Gets an existing Sheet with the given name or returns null
public SheetWrapper GetSheetByName(string Title)
{
Refresh();
var Sheet = SourceSpreadsheet.Sheets.Where(S => S.Properties.Title == Title).FirstOrDefault();
if (Sheet == null)
{
return null;
}
return new SheetWrapper(Service, SpreadsheetID, Sheet);
}
public SheetWrapper AddSheet(string Title, int Index = 0)
{
var Request = new Request();
Request.AddSheet = new AddSheetRequest();
Request.AddSheet.Properties = new SheetProperties();
Request.AddSheet.Properties.Title = Title;
Request.AddSheet.Properties.Index = Index;
var BatchRequest = new BatchUpdateSpreadsheetRequest();
BatchRequest.Requests = new List { Request };
SheetsUtils.TryCheckQuotaSync();
Service.Spreadsheets.BatchUpdate(BatchRequest, SpreadsheetID).Execute();
return GetSheetByName(Title);
}
public SheetWrapper AddSheetAtNextIndexAlphabetically(string Title, int StartIndex = 0)
{
Refresh();
List Sheets = SourceSpreadsheet.Sheets.OrderBy(sheet => sheet.Properties.Index).ToList();
int SheetIndex = StartIndex;
while (SheetIndex < Sheets.Count)
{
if (string.Compare(Title, Sheets[SheetIndex].Properties.Title) < 0)
{
break;
}
SheetIndex++;
}
return AddSheet(Title, SheetIndex);
}
///
/// Queues a new request to add a new sheet to the spreadsheet. Call FlushSpreadsheetBatchRequest() to execute all queued spreadsheet requests.
///
public void BatchAddSheet(string Title, int index = 0)
{
var Request = new Request();
Request.AddSheet = new AddSheetRequest();
Request.AddSheet.Properties = new SheetProperties();
Request.AddSheet.Properties.Title = Title;
Request.AddSheet.Properties.Index = index;
if (UpdateSpreadsheetBatchRequest.Requests == null)
{
UpdateSpreadsheetBatchRequest.Requests = new List();
}
UpdateSpreadsheetBatchRequest.Requests.Add(Request);
}
///
/// Queues a new request to append new columns or rows. Call FlushSpreadsheetBatchRequest() to execute all queued spreadsheet requests.
///
public void BatchAppendDimension(SheetWrapper Sheet, int RowsOrColumnsToAppend = 1, ESheetsDimension Dimension = ESheetsDimension.ROWS)
{
var Request = new Request();
Request.AppendDimension = new AppendDimensionRequest();
Request.AppendDimension.SheetId = Sheet.SheetID;
Request.AppendDimension.Length = RowsOrColumnsToAppend;
Request.AppendDimension.Dimension = Enum.GetName(typeof(ESheetsDimension), Dimension);
if (UpdateSpreadsheetBatchRequest.Requests == null)
{
UpdateSpreadsheetBatchRequest.Requests = new List();
}
UpdateSpreadsheetBatchRequest.Requests.Add(Request);
if (Dimension == ESheetsDimension.ROWS)
{
Sheet.Properties.GridProperties.RowCount += RowsOrColumnsToAppend;
}
else if (Dimension == ESheetsDimension.COLUMNS)
{
Sheet.Properties.GridProperties.ColumnCount += RowsOrColumnsToAppend;
}
}
///
/// Queues a new request to insert new columns or rows. Call FlushSpreadsheetBatchRequest() to execute all queued spreadsheet requests.
///
public void BatchInsertDimension(SheetWrapper Sheet, int RowsOrColumnsToAppend = 1, int StartIndex = 0, ESheetsDimension Dimension = ESheetsDimension.ROWS, bool? bInheritFromBefore = null)
{
var Range = new DimensionRange();
Range.SheetId = Sheet.SheetID;
Range.StartIndex = StartIndex;
Range.EndIndex = StartIndex + RowsOrColumnsToAppend;
Range.Dimension = Enum.GetName(typeof(ESheetsDimension), Dimension);
// create a request to insert the dimension
var Request = new Request();
Request.InsertDimension = new InsertDimensionRequest();
Request.InsertDimension.Range = Range;
Request.InsertDimension.InheritFromBefore = bInheritFromBefore;
if (UpdateSpreadsheetBatchRequest.Requests == null)
{
UpdateSpreadsheetBatchRequest.Requests = new List();
}
UpdateSpreadsheetBatchRequest.Requests.Add(Request);
//update sheet properties
if(Dimension == ESheetsDimension.ROWS)
{
Sheet.Properties.GridProperties.RowCount += RowsOrColumnsToAppend;
}
else if(Dimension == ESheetsDimension.COLUMNS)
{
Sheet.Properties.GridProperties.ColumnCount += RowsOrColumnsToAppend;
}
}
///
/// Queues a new request to set the values of a range of cells. Call FlushValuesBatchRequest() to execute all queued value requests.
///
public void BatchUpdateValueRange(SheetWrapper Sheet, string A1Range, IList> Values, ESheetsDimension MajorDimensions = ESheetsDimension.ROWS)
{
string RefRange = string.Format("\'{0}\'!{1}", Sheet.Title, A1Range);
ValueRange EntryRange = new ValueRange();
EntryRange.Range = RefRange;
EntryRange.MajorDimension = Enum.GetName(typeof(ESheetsDimension), MajorDimensions);
EntryRange.Values = Values;
if(UpdateValuesBatchRequest.Data == null)
{
UpdateValuesBatchRequest.Data = new List();
}
UpdateValuesBatchRequest.Data.Add(EntryRange);
}
///
/// Queues a new request to set the value of a specific cell. Call FlushValuesBatchRequest() to execute all queued value requests.
///
public void BatchUpdateCellValue(SheetWrapper Sheet, string A1Cell, object Value)
{
BatchUpdateValueRange(Sheet, A1Cell, new List> { new List