Unity ver2020.3.38f1 Personal

GoogleCredential credential = GoogleCredential.FromJson(jsonText).CreateScoped(
                DriveService.Scope.DriveReadonly
            	// ,DriveService.Scope.Drive
);

アカウント情報を利用して GoogleDrive にアクセスすると、Android だと以下のエラーとなります。

2023/09/29 21:44:05.541 4080 4294 Info Unity error:Error deserializing JSON credential data.

実際のエラーは、以下のエラーでした。対応策は一番最後を参照して下さい。

2023/09/29 21:44:05.538 4080 4294 Info Unity Error Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type Google.Apis.Auth.OAuth2.JsonCredentialParameters. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'type', line 2, position 7.
2023/09/29 21:44:05.538 4080 4294 Info Unity   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject (Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract objectContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, Newtonsoft.Json.Serialization.JsonProperty containerProperty, System.String id, System.Boolean& createdFromNonDefaultCreator) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 21:44:05.538 4080 4294 Info Unity   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerCo
2023/09/29 21:44:05.540 4080 4294 Info Unity finally

簡単に言うと、コンストラクターが見つからなかったようです。

上記のソースは、Unity のエミュレータでは動作しているので認証情報のJSONに間違いはないです。

Android 特有のエラーと考えられます。

 

ソースを clone して確認すると、以下のソースでエラーがあるのが確認できます。

https://github.com/googleapis/google-api-dotnet-client

./Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs

google-api-dotnet-client % grep -r "Error deserializing JSON credential data" .                 
./Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs:                throw new InvalidOperationException("Error deserializing JSON credential data.", e);
./Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs:                throw new InvalidOperationException("Error deserializing JSON credential data.", e);
./Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs:                throw new InvalidOperationException("Error deserializing JSON credential data.", e);
falco@M1 google-api-dotnet-client % 

以下のコードでエラーが発生していることが考えられます。

JsonCredentialParameters credentialParameters;
try
{   
    credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(json);
}       
catch (Exception e)
{
    throw new InvalidOperationException("Error deserializing JSON credential data.", e);
}
return CreateDefaultCredentialFromParameters(credentialParameters);

ネット検索すると以下のコードを追加しているようなので、追加 (Assets/link.xml)

https://forum.unity.com/threads/error-deserializing-json-credential-data-google-api.1367799/

<linker>
    <assembly fullname="Google.Apis.Auth">
        <type fullname="Google.Apis.Auth.OAuth2.JsonCredentialParameters" preserve="all" />
    </assembly>
</linker>

これで実行すると、次に以下のエラーが発生

2023/09/29 22:21:09.984 11023 11057 Info Unity Error System.NotSupportedException: System.Reflection.Emit.DynamicMethod::.ctor
2023/09/29 22:21:09.984 11023 11057 Info Unity   at System.Reflection.Emit.DynamicMethod..ctor (System.String name, System.Type returnType, System.Type[] parameterTypes, System.Type owner, System.Boolean skipVisibility) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 22:21:09.984 11023 11057 Info Unity   at Newtonsoft.Json.Utilities.DynamicReflectionDelegateFactory.CreateDynamicMethod (System.String name, System.Type returnType, System.Type[] parameterTypes, System.Type owner) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 22:21:09.984 11023 11057 Info Unity   at Newtonsoft.Json.Utilities.DynamicReflectionDelegateFactory.CreateDefaultConstructor[T] (System.Type type) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 22:21:09.984 11023 11057 Info Unity   at Newtonsoft.Json.Serialization.DefaultContractResolver.InitializeContract (Newtonsoft.Json.Serialization.JsonContract contract) [0x00000] in <00000000000000000000000000000000>:0 
2023/09/29 22:21:09.984 11023 11057 Info Unity   at Newtonsoft.Json.Serialization.DefaultContractResolver.CreateObjectContract (System.Type objectType) [0x00000] in <00000000000000000000000000

エラーの原因は以下の模様

Error System.NotSupportedException: System.Reflection.Emit.DynamicMethod::.ctor

再度ネット検索すると、以下がヒット

https://forum.unity.com/threads/notsupportedexception-with-log-detail.543644/

System.Reflection.Emit is not supported with IL2CPP. You will need to use a JSON library that does not require use of it to work with Hololens. I believe that Newtonsoft JSON does have a mode that works with AOT runtimes like IL2CPP.
System.Reflection.Emit は IL2CPP ではサポートされていません。 Hololens と連携するには、JSON ライブラリを使用する必要がありません。 Newtonsoft JSON には、IL2CPP のような AOT ランタイムで動作するモードがあると思います。

結局のところ、Scripting Backend を IL2CPP -> Mono にすることで、正常に動作する事が確認出来ました。

Thanks Unity forum.

これで解決と思いきや、iOS  でも確認しようと設定を切り替えると、iOS は、IL2CPP 固定となり振り出しに戻る。

「 Newtonsoft JSON には、IL2CPP のような AOT ランタイムで動作するモードがあると思います。」

との事なので、結局のところ IL2CPP を使う事で、ストリッピング機能によりリフレクションで関節的にコールしているメソッド等が削除される事が原因のようなので、これを抑止する事にします。

Newtonsoft.Json には、IL2CPP版がありこちらをインストールします。

Newtonsoft.Json-for-Unity

Assets 配下の Link.xml に以下を記述することで、Android、iOS で動作します。

<linker>
    <assembly fullname="Google.Apis.Auth" preserve="all" />
    <assembly fullname="Google.Apis.Drive.v3" preserve="all" />
    <assembly fullname="Newtonsoft.Json" preserve="all" />
</linker>

以下、抜粋のGoogle Drive にアクセスするソースです。

void Start()
{
    ListFilesRecursively("folderId");
}

private void InitGooglDrive()
{
    String jsonText = Resources.Load<TextAsset>("GoogleAuth/service_account").text;
    Debug.Log($"json:{jsonText}");

    JsonCredentialParameters credentialParameters;

    try {
        credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize<JsonCredentialParameters>(jsonText);
    } catch (Exception e) {
        Debug.Log($"Error {e}");
    } finally {
        Debug.Log($"finally");
    }

    // OAuth 2.0認証をセットアップ
    Debug.Log("Call FromStream");
    GoogleCredential credential = GoogleCredential.FromJson(jsonText).CreateScoped(
        DriveService.Scope.DriveReadonly
    );

    // Drive APIクライアントを作成
    service = new DriveService(new BaseClientService.Initializer() {
        HttpClientInitializer = credential,
        ApplicationName = "Google shared folder viewer"
    });

}

private async void ListFilesRecursively(string folderId)
{
    InitGooglDrive();
    Debug.Log("folder:" + folderId);
    FilesResource.ListRequest listRequest = service.Files.List();
    listRequest.Q = $"'{folderId}' in parents";
    listRequest.Fields = "nextPageToken, files(id, name, mimeType, modifiedTime, md5Checksum)";

    do {
	var files = listRequest.Execute().Files;
	if (files == null || files.Count == 0) {
	    Debug.Log("No files found in folder: " + folderId);
	    continue;
	}

	foreach (var file in files) {
	    Debug.Log($"File Name: {file.Name}, ID: {file.Id}, MIME Type: {file.MimeType}, createdTime:{file.CreatedTime}");
   	    // フォルダの場合、再帰的にその中身を表示
 	    if (file.MimeType == "application/vnd.google-apps.folder") {
		ListFilesRecursively(file.Id);
  	    }
	 }
         if(listRequest.PageToken != null) {
             // next page token があればスリープ
             await Task.Delay(200);
         }
     } while (!string.IsNullOrEmpty(listRequest.PageToken));
}

 

Flutter で Google drive の createdTime, modifiedTime を取得するには?

 

$fields を明示しないと取得できなかった。

Future<void> _test(String folderId) async {
  final jsonString = await rootBundle.loadString(
      'assets/google_sheet_auth.json');
  final jsonData = json.decode(jsonString);
  final credentials = ServiceAccountCredentials.fromJson(jsonData);
  final scopes = [
    drive.DriveApi.driveReadonlyScope,
    // drive.DriveApi.driveMetadataReadonlyScope,
    // SheetsApi.spreadsheetsReadonlyScope,
  ];
  final client = await clientViaServiceAccount(credentials, scopes);
  var driveApi = drive.DriveApi(client);

  drive.FileList files = await driveApi.files.list(
    q: "'$folderId' in parents",
    $fields: 'files(id, name, mimeType, createdTime, modifiedTime)',
  );
  for (drive.File file in files.files ?? []) {
    log.d('${file.name} createdTime: ${file.createdTime} modifiedTime: ${file.modifiedTime}');
  }

  try {
    final _file = await driveApi.files.get(folderId,
      $fields: 'files(id, name, mimeType, createdTime, modifiedTime)',
    ) as drive.File;
    // ファイル情報が取得できた場合の処理
    log.d('${_file.name} createdTime: ${_file.createdTime} modifiedTime: ${_file.modifiedTime} ');
  } catch (e) {
    // ファイルが見つからなかった場合やエラーが発生した場合の処理
    print('エラー: $e');
  }

  client.close();
}

 

ChatGPTに聞くと

google drive のファイルのタイムスタンプを取得する事はできますか?

はい、Google Driveのファイルのタイムスタンプを取得することは可能です。Google Drive APIを使用してファイルのメタデータを取得することで、ファイルの作成日時や最終更新日時などの情報を取得できます。

以下は、Google Driveファイルのメタデータを取得して、作成日時と最終更新日時を取得するDartコードの例です。

import 'package:googleapis/drive/v3.dart' as drive;
import 'package:googleapis_auth/auth_io.dart';

Future<void> getFileTimestamp(String fileId) async {
  final jsonString = await rootBundle.loadString('assets/google_sheet_auth.json');
  final jsonData = json.decode(jsonString);
  final credentials = ServiceAccountCredentials.fromJson(jsonData);
  final scopes = [drive.DriveApi.driveMetadataReadonlyScope];

  final client = await clientViaServiceAccount(credentials, scopes);
  final driveApi = drive.DriveApi(client);

  try {
    final file = await driveApi.files.get(fileId);
    final createdTime = file.createdTime;
    final modifiedTime = file.modifiedTime;

    print('ファイルの作成日時: $createdTime');
    print('ファイルの最終更新日時: $modifiedTime');
  } catch (e) {
    print('エラー: $e');
  } finally {
    client.close();
  }
}

void main() {
  getFileTimestamp('YOUR_GOOGLE_DRIVE_FILE_ID');
}

ってことで、上記では取得できませんでした。

© 2024 Falco Tech Blog Suffusion theme by Sayontan Sinha