I am trying to retrieve filename and ID list with Google Drive API (the latest Drive API version v3 is used here) as a first step in order to analyze and manipulate these files on Google Drive. The experimental implementation is as below. (Note: credentials.json
file is needed and the step to generate this file is here. )
The experimental implementation
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Drive.v3.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace GoogleDriveFileManager
{
class Program
{
static string[] Scopes = { DriveService.Scope.DriveReadonly };
static string ApplicationName = "GoogleDriveFileManager";
static void Main(string[] args)
{
UserCredential credential;
using (var stream =
new FileStream("credentials.json", FileMode.Open, FileAccess.Read))
{
string credPath = "token.json";
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine($"Credential file saved to: {credPath}");
}
// Create Drive API service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
// Define parameters of request.
FilesResource.ListRequest listRequest = service.Files.List();
listRequest.PageSize = 1000;
listRequest.Fields = "nextPageToken, files(id, name)";
// List files.
IList<Google.Apis.Drive.v3.Data.File> files = listRequest.Execute()
.Files;
Console.WriteLine("Files:");
Console.WriteLine($"file.Name{new String('\t', 6)}file.Id{new String('\t', 8)}file.ModifiedTime");
if (files != null && files.Count > 0)
{
foreach (var file in files)
{
Console.WriteLine(
$"{file.Name.PadRight(56)}{file.Id.PadRight(36)}{file.ModifiedTime}"
);
}
}
else
{
Console.WriteLine("No files found.");
}
Console.Read();
}
}
}
If there is any possible improvement about potential drawback or unnecessary overhead, please let me know.
1 Answer 1
First of all let me clarify that I'm not familiar with the Google Drive API. I've done some refactoring but I haven't tested it (just make it compile).
If I can assume you are using greater C# version than 8 then you can refactor your code like this:
using static {YourMainNamespace}.Constants;
class Program
{
static async Task Main(string[] args)
{
UserCredential credential = await AuthorizeAgainstGoogleDriveAsync();
IList<Google.Apis.Drive.v3.Data.File> files = await GetFilesMetaInfoAsync(credential);
DisplayFilesMetaInfoInTabularFashion(files);
Console.ReadLine();
}
static async Task<UserCredential> AuthorizeAgainstGoogleDriveAsync(
string sourceRelativePath = Authorize.RelativeFilePath.Source,
string targetRelativePath = Authorize.RelativeFilePath.Target)
{
using var stream = new FileStream(sourceRelativePath, FileMode.Open, FileAccess.Read);
var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Authorize.Scopes,
Authorize.User,
CancellationToken.None,
new FileDataStore(targetRelativePath, true));
Console.WriteLine($"Credential file saved to: {Authorize.RelativeFilePath.Target}");
return credential;
}
static async Task<IList<Google.Apis.Drive.v3.Data.File>> GetFilesMetaInfoAsync(
UserCredential credential,
string applicationName = GetFiles.ApplicationName,
int pageSize = GetFiles.PageSize,
string fields = GetFiles.Fields)
{
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = applicationName,
});
FilesResource.ListRequest listRequest = service.Files.List();
listRequest.PageSize = pageSize;
listRequest.Fields = fields;
var fileList = await listRequest.ExecuteAsync();
return fileList.Files;
}
static void DisplayFilesMetaInfoInTabularFashion(IList<Google.Apis.Drive.v3.Data.File> files)
{
Console.WriteLine("Files:");
Console.WriteLine($"{DisplayFiles.Name}{new string('\t', 6)}{DisplayFiles.Id}{new string('\t', 8)}{DisplayFiles.ModifiedTime}");
if (files == null || files.Count <= 0)
{
Console.WriteLine(DisplayFiles.NoFileMessage);
return;
}
foreach (var file in files)
{
Console.WriteLine($"{file.Name,-56}{file.Id,-36}{file.ModifiedTime}");
}
}
}
static class Constans
{
public static class Authorize
{
public const string User = "user";
public readonly static string[] Scopes = new[] { DriveService.Scope.DriveReadonly };
public static class RelativeFilePath
{
public const string Source = "credentials.json";
public const string Target = "token.json";
}
}
public static class GetFiles
{
public const string ApplicationName = "GoogleDriveFileManager";
public const int PageSize = 1000;
public const string Fields = "nextPageToken, files(id, name)";
}
public static class DisplayFiles
{
private const string prefix = "file.";
public const string Name = prefix + nameof(Name);
public const string Id = prefix + nameof(Id);
public const string ModifiedTime = prefix + nameof(ModifiedTime);
public const string NoFileMessage = "No files found.";
}
}
Let me give you some context for each part:
Main
static async Task Main(string[] args)
{
UserCredential credential = await AuthorizeAgainstGoogleDriveAsync();
IList<Google.Apis.Drive.v3.Data.File> files = await GetFilesMetaInfoAsync(credential);
DisplayFilesMetaInfoInTabularFashion(files);
Console.ReadLine();
}
- I've made your
Main
async (supported since C# 7.1) to be able to useasync
-await
- I've split your logic into three smaller functions to make your
Main
short and concise- If the scope of the application is this small then you can use shorter names of course, like
AuthorizeAsync
GetFiles
DisplayFiles
- If the scope of the application is this small then you can use shorter names of course, like
Constants
using static {YourMainNamespace}.Constants;
...
static class Constans
{
public static class Authorize
{
public const string User = "user";
public readonly static ImmutableArray<string> Scopes = new[] { DriveService.Scope.DriveReadonly }.ToImmutableArray();
public static class RelativeFilePath
{
public const string Source = "credentials.json";
public const string Target = "token.json";
}
}
public static class GetFiles
{
public const string ApplicationName = "GoogleDriveFileManager";
public const int PageSize = 1000;
public const string Fields = "nextPageToken, files(id, name)";
}
public static class DisplayFiles
{
private const string prefix = "file.";
public const string Name = prefix + nameof(Name);
public const string Id = prefix + nameof(Id);
public const string ModifiedTime = prefix + nameof(ModifiedTime);
public const string NoFileMessage = "No files found.";
}
}
- I've put all of the hardcoded values into a hierarchical structure
- Because I'm unfamiliar with the domain I've organize them based on the usage
- In normal cases you should organize them based on their semantics
- I've used
const
(which is implicitstatic
) to prevent modification - In case of
Scope
I've usedImmutableArray
to prevent modification- Here I can't use
const
but it could (and should) be marked asreadonly
- Here I can't use
- I've used
using static
to avoid prefixing withConstants
each and every time
AuthorizeAgainstGoogleDriveAsync
static async Task<UserCredential> AuthorizeAgainstGoogleDriveAsync(
string sourceRelativePath = Authorize.RelativeFilePath.Source,
string targetRelativePath = Authorize.RelativeFilePath.Target)
{
using var stream = new FileStream(sourceRelativePath, FileMode.Open, FileAccess.Read);
var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Authorize.Scopes,
Authorize.User,
CancellationToken.None,
new FileDataStore(targetRelativePath, true));
Console.WriteLine($"Credential file saved to: {Authorize.RelativeFilePath.Target}");
return credential;
}
- I've made the source and target file paths configurable (but with default values)
- If you wish you can make the same with user and scopes
- One can argue whether the
Console.WriteLine
should belong here or inside theMain
- I've put it here to make the main as concise as possible
- But from reusability point of view it should not belong here
- I've used using declaration (which is a C# 8 feature) to avoid the block operator (
{ ... }
)
GetFilesMetaInfoAsync
static async Task<IList<Google.Apis.Drive.v3.Data.File>> GetFilesMetaInfoAsync(
UserCredential credential,
string applicationName = GetFiles.ApplicationName,
int pageSize = GetFiles.PageSize,
string fields = GetFiles.Fields)
{
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = applicationName,
});
FilesResource.ListRequest listRequest = service.Files.List();
listRequest.PageSize = pageSize;
listRequest.Fields = fields;
var fileList = await listRequest.ExecuteAsync();
return fileList.Files;
}
- I've made this API call to async (
Execute
>>ExecuteAsync
) to make use of the async non-blocking I/O - I've declared the function with three optional parameters to be able to customize the request if needed
- I've realized that you are not making use of the
nextPageToken
but I did not remove it- I've assumed that the implementation of paging will be your next step
DisplayFilesMetaInfoInTabularFashion
static void DisplayFilesMetaInfoInTabularFashion(IList<Google.Apis.Drive.v3.Data.File> files)
{
Console.WriteLine("Files:");
Console.WriteLine($"{DisplayFiles.Name}{new string('\t', 6)}{DisplayFiles.Id}{new string('\t', 8)}{DisplayFiles.ModifiedTime}");
if (files == null || files.Count <= 0)
{
Console.WriteLine(DisplayFiles.NoFileMessage);
return;
}
foreach (var file in files)
{
Console.WriteLine($"{file.Name,-56}{file.Id,-36}{file.ModifiedTime}");
}
}
- This operation is tightly coupled to the request
- It highly relies on the retrieved
fields
- So, it might make sense to it make more dynamic if needed
- It highly relies on the retrieved
- I've used
new string
instead ofnew String
- I suggest to read this article
- I've reverted the
if
statement to use the early exit pattern- With this the main logic (
foreach
) is not indented
- With this the main logic (
- I've also used the capabilities of string interpolation to avoid calls like
PadRight
Explore related questions
See similar questions with these tags.
async main
is supported. \$\endgroup\$