using Google.Apis.Auth.OAuth2; using Google.Apis.Gmail.v1; using Google.Apis.Services; using Google.Apis.Sheets.v4; using Google.Apis.Sheets.v4.Data; using Google.Apis.Util.Store; using System.Net.Mail; using System.Net.Mime; using System.Reflection; namespace AiQ_GUI { internal class GoogleAPI { public static UserCredential credential; public static SheetsService service = new(); const string ApplicationName = "Google Sheets API .NET Quickstart"; static readonly string credPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); public const string spreadsheetId_ModelInfo = "1bCcCr4OYqfjmydt6UqtmN4FQETezXmZRSStJdCCcqZM"; public const string DrivePath = @"G:\Shared drives\MAV Production GUI's\"; // Path to google shared drive // Startup and make necessary connections to Google servers and make sure user is logged in public static bool Setup() { try { string streamPath = $"{DrivePath}R50IQ\\client_secret.json"; string[] Scopes = [SheetsService.Scope.Spreadsheets]; FileStream stream = new(streamPath, FileMode.Open, FileAccess.Read); credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromStream(stream).Secrets, Scopes, "user", CancellationToken.None, new FileDataStore(credPath, true)).Result; service = new SheetsService(new BaseClientService.Initializer() { HttpClientInitializer = credential, ApplicationName = ApplicationName, }); return true; } catch { return false; } } // Write a 1D array range to a spreadsheet public static void WriteToSS(List ToWrite, string Range, string SSID) { ValueRange ValueRange = new() { Values = [ToWrite] }; SpreadsheetsResource.ValuesResource.UpdateRequest Update = service.Spreadsheets.Values.Update(ValueRange, SSID, Range); Update.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.RAW; Update.Execute(); } // Read a range from a spreadsheet, always returns a 2D object even if 1D is requested public static IList> ReadSS(string sheet, string Location) { return service.Spreadsheets.Values.Get(sheet, Location).Execute().Values; } // Checks the WIP columns of a given serial number public static bool CheckWIP(string SerialNumber, string spreadsheetId) { int Row = CheckSerialNumRow(spreadsheetId, SerialNumber); // Get row of serial number - 1 because don't want next row in this case IList> valuesRow = ReadSS(spreadsheetId, $"S{Row}"); // Check if that row is WIP or not return Convert.ToBoolean(valuesRow[0][0]); } // Update serial number register with relevant details about the camera that has passed public static string UpdateSpreadSheetPreTest(string spreadsheetId, Versions Vers, string CamDesc, string ModelOnTest) { try { // Finds next row to be used by length of returned array and add 1 IList> values = service.Spreadsheets.Values.Get(spreadsheetId, "B1:B").Execute().Values; int nextRow = values != null ? values.Count + 1 : 0; if (values?.Count > 0) { // Serial number location = nextRow - 2 (Array starts at 0 but spreadsheet starts at 1 & next row has already added one so need to get rid) string lastSerialNumber = Convert.ToString(values[nextRow - 2][0]); int NewSerialNumberInt = Convert.ToInt32(lastSerialNumber.Substring(1)) + 1; // Generate new serial number knowing the last string newSerialNumber = "K" + NewSerialNumberInt.ToString(); // Send data to spreadsheet and set WIP to TRUE List oblistAE = // Write columns A-E [ ModelOnTest, newSerialNumber, CamDesc, "Pre Test: " + DateTime.Now.ToString("dd/MM/yyyy"), Vers.version + " - " + Vers.revision + Environment.NewLine + Vers.buildtime + Environment.NewLine + Vers.proquint, ]; WriteToSS(oblistAE, $"A{nextRow}:E{nextRow}", spreadsheetId); // Write MAC to column H List oblistH = [Vers.MAC]; WriteToSS(oblistH, $"H{nextRow}", spreadsheetId); List oblistN = [$"GUI Version: {GUIUpdate.GUIVerShort}"]; // Write column N WriteToSS(oblistN, $"N{nextRow}", spreadsheetId); // Write TRUE to WIP checkbox in column S List oblistS = ["TRUE"]; WriteToSS(oblistS, $"S{nextRow}", spreadsheetId); return newSerialNumber; } else { return "Last serial number not found"; } } catch (Exception ex) { return $"ERROR: {ex.Message}"; } } // Update serial number register with relevant details about the camera that has passed public static string UpdateSpreadSheetRePreTest(string spreadsheetId, Versions Vers) { try { int CamRow = CheckSerialNumRow(spreadsheetId, Vers.Serial); if (CamRow != 0) { // Send data to spreadsheet and set WIP to TRUE List oblistDE = // Write columns D-E [ "Pre Test: " + DateTime.Now.ToString("dd/MM/yyyy"), Vers.version + " - " + Vers.revision + Environment.NewLine + Vers.buildtime + Environment.NewLine + Vers.proquint, ]; WriteToSS(oblistDE, $"D{CamRow}:E{CamRow}", spreadsheetId); // Write MAC to column H List oblistH = [Vers.MAC]; WriteToSS(oblistH, $"H{CamRow}", spreadsheetId); List oblistN = [$"GUI Version: {GUIUpdate.GUIVerShort}"]; // Write column N WriteToSS(oblistN, $"N{CamRow}", spreadsheetId); // Write TRUE to WIP checkbox in column S List oblistS = ["TRUE"]; WriteToSS(oblistS, $"S{CamRow}", spreadsheetId); return "OK"; } else { return "Serial number not found"; } } catch (Exception ex) { return $"ERROR: {ex.Message}"; } } // Update serial number register with relevant details about the camera that has passed public static string UpdateSpreadSheetFinalTest(string spreadsheetId, Diags DiagsAPI, SSHData sshData, int RMANum) { try { int CamRow = CheckSerialNumRow(spreadsheetId, DiagsAPI.serialNumber); IList> valuesRow = ReadSS(spreadsheetId, $"D{CamRow}"); string TestDate = Convert.ToString(valuesRow[0][0]) + Environment.NewLine + "Final Test: " + DateTime.Now.ToString("dd/MM/yyyy"); if (RMANum != 0) TestDate = TestDate.Replace("Final", "RMA"); // So it will say RMA Test in the spreadsheet. // Write column D List oblistD = [TestDate]; WriteToSS(oblistD, $"D{CamRow}", spreadsheetId); List oblistFG = // Write columns F-G [ DiagsAPI.licenses.raptorKeyID, sshData.packages ]; WriteToSS(oblistFG, $"F{CamRow}:G{CamRow}", spreadsheetId); List oblistNR = // Write columns N-S [ $"GUI Version: {GUIUpdate.GUIVerShort}", DiagsAPI.licenses.saf1, DiagsAPI.licenses.audit, DiagsAPI.licenses.stream, sshData.tailscale, "FALSE" // Write FALSE to WIP checkbox in column S ]; WriteToSS(oblistNR, $"N{CamRow}:S{CamRow}", spreadsheetId); return string.Empty; } catch (Exception ex) { return "Failed to update spreadsheet data, please check manually" + ex.Message; } } // Update Vaxtor spreadsheet public static string UpdateSpreadSheetVaxtor(VaxtorLic VaxtorLicResp, string serial, string model) { try { string spreadsheetId = "1n5zhmI4Tz6JFr0stLNFOR6GsxGEKBEhrVKQ6yncM-LA"; int nextRow = CheckNextFree(spreadsheetId); List oblistCF = // Write columns C-F [ model, serial, DateTime.Now.ToString("dd/MM/yyyy"), VaxtorLicResp.protectionKeyId, ]; WriteToSS(oblistCF, $"C{nextRow}:F{nextRow}", spreadsheetId); // Write PROD to column H List oblistH = ["PROD"]; WriteToSS(oblistH, $"H{nextRow}", spreadsheetId); return string.Empty; } catch (Exception ex) { return "Failed to update spreadsheet data, please check manually" + ex.Message; } } // Checks RMA control sheet for a model and serial that match current camera public static int CheckRMANum(string serial, string model) { string spreadsheetId_RMAControl = "1tZhkYrqBQ3BcL7ZS4q3ghzCgHSJ8f5LVSj7nh6fIRC8"; try { // Get all info in H and I columns IList> valuesRMA = service.Spreadsheets.Values.Get(spreadsheetId_RMAControl, "H2:I").Execute().Values; for (int i = 0; i < valuesRMA.Count; i++) { try // In case line is blank { // Checks is serial and model num in RMA control spreadsheet match what is in the camera if (valuesRMA[i][0].ToString().Contains(serial) && valuesRMA[i][1].ToString().Contains(model)) { int OnRow = i + 2; // Offset for start of the sheet and starting at 1 valuesRMA = service.Spreadsheets.Values.Get(spreadsheetId_RMAControl, "A" + OnRow).Execute().Values; return Convert.ToInt16(valuesRMA[0][0]); // Once it has found the serial it gusses at the RMA number } } catch { } } } catch { } return 0; // If it can't be found } // Checks RMA control sheet for a model and serial that match current camera public static int CheckSerialNumRow(string spreadsheetId, string serial) { // Get all info in B column IList> valuesRMA = service.Spreadsheets.Values.Get(spreadsheetId, "B:B").Execute().Values; for (int i = 0; i < valuesRMA.Count; i++) { try // In case line is blank { // Checks is serial and model num in RMA control spreadsheet match what is in the camera if (valuesRMA[i][0].ToString().Contains(serial)) { return i + 1; // Offset for sheet starting at 1 } } catch { } } return 0; // If it can't be found } // Checks Vaxtor sheet for next free row public static int CheckNextFree(string spreadsheetId) { IList> valuesRMA = service.Spreadsheets.Values.Get(spreadsheetId, "C2:C").Execute().Values; // Get how many C cows return valuesRMA.Count + 2; // Gets the amount of rows, offsets for start from 1 error and adds one to be next row } public static void EmailApproval(string ApprovalRow, string User) { FileStream GmailStream = new($"{DrivePath}R50IQ\\creds.json", FileMode.Open, FileAccess.Read); string[] ScopesGmail = [GmailService.Scope.GmailSend]; using (GmailStream) { string credPathGmail = Path.Combine(credPath, ".credentials/gmail-dotnet-quickstart.json"); credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromStream(GmailStream).Secrets, ScopesGmail, "user", CancellationToken.None, new FileDataStore(credPathGmail, true)).Result; GmailStream.Close(); } // Build the MIME message with attachment using MailMessage mail = new MailMessage(); mail.From = new MailAddress("me"); mail.To.Add("richard.porter@mav-systems.com"); mail.To.Add("bradley.relyea@mav-systems.com"); mail.To.Add("bradley.born@mav-systems.com"); mail.Subject = "Approval required"; mail.Body = $"Dear Rich,

Camera needs approval
" + $"https://docs.google.com/spreadsheets/d/1bCcCr4OYqfjmydt6UqtmN4FQETezXmZRSStJdCCcqZM/edit#gid=1931079354&range=A{ApprovalRow}" + $"


Thanks,

{User}"; mail.IsBodyHtml = true; // Attach the log file if it exists string logFilePath = LDS.MAVPath + Logging.LogFileName; if (File.Exists(logFilePath)) { Attachment logAttachment = new(logFilePath, MediaTypeNames.Text.Plain); logAttachment.Name = Logging.LogFileName; mail.Attachments.Add(logAttachment); } // Save the MIME message to a stream using MemoryStream ms = new(); SmtpClient smtpClient = new(); // Only used to access the internal Write method Type mailWriterType = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter"); object? mailWriter = Activator.CreateInstance( mailWriterType, BindingFlags.Instance | BindingFlags.NonPublic, null, [ms, true], null); typeof(MailMessage).InvokeMember( "Send", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, mail, [mailWriter, true, true]); ms.Position = 0; byte[] rawBytes = ms.ToArray(); GmailService service = new(new BaseClientService.Initializer() { HttpClientInitializer = credential, ApplicationName = ApplicationName, }); Google.Apis.Gmail.v1.Data.Message newMsg = new() { Raw = Convert.ToBase64String(rawBytes) .Replace("+", "-") .Replace("/", "_") .Replace("=", "") }; service.Users.Messages.Send(newMsg, "me").Execute(); } } }