Skip to content

Instantly share code, notes, and snippets.

@rakisaionji
Last active October 13, 2023 02:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rakisaionji/67a3a7517b768b3e063daf751bbe3de9 to your computer and use it in GitHub Desktop.
Save rakisaionji/67a3a7517b768b3e063daf751bbe3de9 to your computer and use it in GitHub Desktop.
Automatically download a specific folder from Google Drive with Firebase, shared permission to Firebase SDK is required.
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
static class Program
{
private static string ComputeMd5Checksum(Stream inputStream)
{
var md5 = HashAlgorithm.Create("MD5");
inputStream.Seek(0, SeekOrigin.Begin);
var md5Hash = md5.ComputeHash(inputStream);
return BitConverter.ToString(md5Hash).Replace("-", String.Empty).ToLower();
}
private static string ReplaceInvalidChars(string filename)
{
return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));
}
private static string GetRelativePath(string path, string root)
{
var pathUri = new Uri(path);
if (!root.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
root += Path.DirectorySeparatorChar;
}
var folderUri = new Uri(root);
return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar));
}
private static DriveService AuthenticateServiceAccount(string serviceAccountEmail, string serviceAccountCredentialFilePath)
{
try
{
if (string.IsNullOrEmpty(serviceAccountCredentialFilePath))
throw new Exception("Path to the service account credentials file is required.");
if (!File.Exists(serviceAccountCredentialFilePath))
throw new Exception("The service account credentials file does not exist at: " + serviceAccountCredentialFilePath);
if (string.IsNullOrEmpty(serviceAccountEmail))
throw new Exception("ServiceAccountEmail is required.");
string[] scopes = new string[] { DriveService.Scope.Drive };
if (Path.GetExtension(serviceAccountCredentialFilePath).ToLower() == ".json")
{
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream).CreateScoped(scopes);
}
return new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Google Drive Service",
});
}
else if (Path.GetExtension(serviceAccountCredentialFilePath).ToLower() == ".p12")
{
var certificate = new X509Certificate2(serviceAccountCredentialFilePath, "notasecret", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
var serviceAccount = new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes
};
var credential = new ServiceAccountCredential(serviceAccount.FromCertificate(certificate));
return new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Google Drive Service",
});
}
else
{
throw new Exception("Unsupported Service accounts credentials.");
}
}
catch (Exception ex)
{
Console.WriteLine("Create service account DriveService failed" + ex.Message);
throw new Exception("CreateServiceAccountDriveFailed", ex);
}
}
private static Google.Apis.Drive.v3.Data.FileList ListAll(DriveService service, string fileId)
{
try
{
if (service == null) throw new ArgumentNullException("service");
var request = service.Files.List();
request.PageSize = 1000;
request.Q = "'" + fileId + "' in parents";
request.Fields = "files(id,name,kind,mimeType,createdTime,modifiedTime,size,md5Checksum)";
var pageStreamer = new Google.Apis.Requests.PageStreamer<Google.Apis.Drive.v3.Data.File, FilesResource.ListRequest, Google.Apis.Drive.v3.Data.FileList, string>(
(req, token) => request.PageToken = token,
response => response.NextPageToken,
response => response.Files);
var allFiles = new Google.Apis.Drive.v3.Data.FileList();
allFiles.Files = new List<Google.Apis.Drive.v3.Data.File>();
foreach (var result in pageStreamer.Fetch(request))
{
allFiles.Files.Add(result);
}
return allFiles;
}
catch (Exception Ex)
{
throw new Exception("Request Files.List failed.", Ex);
}
}
private static Google.Apis.Drive.v3.Data.File GetFile(DriveService service, string fileId)
{
try
{
if (service == null) throw new ArgumentNullException("service");
var request = service.Files.Get(fileId);
request.Fields = "id,name,kind,mimeType,createdTime,modifiedTime,size";
return request.Execute();
}
catch (Exception Ex)
{
throw new Exception("Request Files.List failed.", Ex);
}
}
private static MemoryStream DriveDownloadFile(DriveService service, string fileId)
{
var request = service.Files.Get(fileId);
var stream = new MemoryStream();
request.Download(stream);
return stream;
}
private static MemoryStream DriveExportFile(DriveService service, string fileId, string gappsMimeType, ref string path, out string md5)
{
var extension = "";
var exportMimeType = "application/pdf";
switch (gappsMimeType)
{
case "application/vnd.google-apps.document":
extension = ".docx";
exportMimeType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
break;
case "application/vnd.google-apps.spreadsheet":
extension = ".xlsx";
exportMimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
break;
case "application/vnd.google-apps.presentation":
extension = ".pptx";
exportMimeType = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
break;
case "application/vnd.google-apps.drawing":
extension = ".svg";
exportMimeType = "image/svg+xml";
break;
case "application/vnd.google-apps.script":
extension = ".json";
exportMimeType = "application/vnd.google-apps.script+json";
break;
default:
break;
}
if (!path.EndsWith(extension))
{
var lastPoint = path.LastIndexOf(".");
var lastSlash = path.LastIndexOf("\\");
if (lastPoint > 0 && lastSlash > 0 && lastSlash < lastPoint)
{
path = path.Substring(0, lastPoint);
}
path = path + extension;
}
var request = service.Files.Export(fileId, exportMimeType);
var stream = new MemoryStream();
request.Download(stream);
md5 = ComputeMd5Checksum(stream);
return stream;
}
private static MemoryStream DriveDownloadOrExportFile(DriveService service, Google.Apis.Drive.v3.Data.File file, ref string path, out string md5)
{
if (file.MimeType.StartsWith("application/vnd.google-apps"))
{
return DriveExportFile(service, file.Id, file.MimeType, ref path, out md5);
}
else
{
md5 = file.Md5Checksum;
return DriveDownloadFile(service, file.Id);
}
}
private static void UpdateModifiedTime(this DirectoryInfo directory, DateTimeOffset? dateTimeOffset)
{
try
{
if (dateTimeOffset.HasValue)
{
directory.CreationTime = dateTimeOffset.Value.DateTime;
directory.LastWriteTime = dateTimeOffset.Value.DateTime;
}
}
catch (Exception)
{
}
}
private static void UpdateModifiedTime(this FileInfo file, DateTimeOffset? dateTimeOffset)
{
try
{
if (dateTimeOffset.HasValue)
{
file.CreationTime = dateTimeOffset.Value.DateTime;
file.LastWriteTime = dateTimeOffset.Value.DateTime;
}
}
catch (Exception)
{
}
}
private static void Recursive(DriveService service, string fileId, string localPath = "", Dictionary<string, string> checksum = null)
{
var folder = ListAll(service, fileId);
foreach (var item in folder.Files)
{
var time = item.ModifiedTimeDateTimeOffset;
var path = Path.Combine(localPath.Trim(), ReplaceInvalidChars(item.Name.Trim()));
if (item.MimeType.Equals("application/vnd.google-apps.folder"))
{
Console.WriteLine("[+] {0}", path);
var localDir = new DirectoryInfo(path);
if (!localDir.Exists) localDir.Create();
Recursive(service, item.Id, path, checksum);
localDir.UpdateModifiedTime(time);
}
else
{
Console.WriteLine(" {0}", path);
using (var downloadStream = DriveDownloadOrExportFile(service, item, ref path, out string md5))
using (var fileStream = File.Open(path, FileMode.Create, FileAccess.Write))
{
if (checksum != null)
{
if (checksum.ContainsKey(path)) checksum[path] = md5;
else checksum.Add(path, md5);
}
downloadStream.Seek(0, SeekOrigin.Begin);
downloadStream.CopyTo(fileStream);
fileStream.Flush();
fileStream.Close();
}
var localFile = new FileInfo(path);
localFile.UpdateModifiedTime(time);
}
}
}
private static void WriteChecksum(string outputPath, Dictionary<string, string> checksum, string rootPath = null)
{
using (var output = new StreamWriter(outputPath, false))
{
foreach (var item in checksum)
{
var path = item.Key;
var hash = item.Value;
if (String.IsNullOrEmpty(hash))
{
continue;
}
if (!String.IsNullOrEmpty(rootPath))
{
path = GetRelativePath(path, rootPath);
}
output.WriteLine("{0,-40} *{1}", hash, path);
}
output.Flush();
}
}
static void Main(string[] args)
{
var driveId = "################";
var service = AuthenticateServiceAccount(
"firebase@test.iam.gserviceaccount.com",
"serviceAccountCredentials.json"
);
var destination = "Y:\\";
var checksum = new Dictionary<string, string>();
var rootDir = GetFile(service, driveId);
var rootName = Path.Combine(destination, ReplaceInvalidChars(rootDir.Name.Trim()));
var rootMd5 = Path.Combine(destination, String.Concat(ReplaceInvalidChars(rootDir.Name.Trim()), ".md5"));
Console.WriteLine("[*] {0}", rootName);
var localRoot = new DirectoryInfo(rootName);
var localTime = rootDir.ModifiedTimeDateTimeOffset;
if (!localRoot.Exists) localRoot.Create();
Recursive(service, driveId, rootName, checksum);
localRoot.UpdateModifiedTime(localTime);
WriteChecksum(rootMd5, checksum, destination);
new FileInfo(rootMd5).UpdateModifiedTime(localTime);
Console.WriteLine("DONE!");
Console.ReadLine();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment