Last active
October 13, 2023 02:42
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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