WatsonのUnity-SDKから消えたConfiguration Editorを調査し、今後の方針を考えてみる(1/2)

IBM の [developerWorks](https://www.ibm.com/developerworks/jp/) サイトの記事 [Watson×Unity！初心者でもできる、VR 空間で Unity ちゃんとおしゃべりアプリ！](https://www.ibm.com/developerworks/jp/cloud/library/unity/) を現時点(2018年4月15日)の最新環境(バージョン)で試してみました、ところ… 初っ端から Configuration Editor が無くて困ってしまいました。

その顛末は [試してみる](https://qiita.com/yamachan360/items/911204e57957bd4bbef0) や [続・試してみる](https://qiita.com/yamachan360/items/bcfa10b2d9bf62df8300) にまとめて、書き直したサンプルプログラムを公開しました。

とりあえず落ち着きましたので、そもそもどうしてこうなったのか？どう対応するのが正解なのか？を2回にわけてちゃんと考えたいとおもいます。[後編](https://qiita.com/yamachan360/items/a36c57e99a68597b8b0d) も公開しました。

# 消えたConfiguration Editor

元の [dW記事](https://www.ibm.com/developerworks/jp/cloud/library/unity/) は公開が昨年の9月と古いもので Unity 5.6.2f1 に、Watson Unity-SDK v0.13.0 を使用していて、以下のメニューがあります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/23a1b77a-e98a-c82b-36b6-c889afdeca28.png)

私の環境は Unity 2017.4.0f1 に、Watson Unity-SDK v2.2.1 を使用していて、以下のメニューしかありません。

![私の環境の場合](https://camo.qiitausercontent.com/f7e40d057ff1ed60b35164fd655a5f2d7e51acc9/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3139333531392f63626635316164332d383838392d346536662d323634622d3932386430313737373330332e706e67)

今回のdW記事でよく使用しているConfiguration Editorが無いので、いきなり最初で詰まってしまいますね。どこに行ってしまったのでしょう？

[以前の投稿](https://qiita.com/yamachan360/items/911204e57957bd4bbef0) にまとめましたが、便利ツールであるConfiguration Editorを使わず、利用するコード側でCredentials管理をするのがv1.0.0以降のお作法であり、正解なのかな？が私の結論です。

でもConfiguration Editorって、実際には何をしてくれていたのでしょうか？そして今後はどう実装したら良いのでしょうか？

# Unityエディター拡張から探る

[エディター拡張入門](https://anchan828.github.io/editor-manual/web/index.html) サイトの情報が役に立ちます。学びつつ、探っていきましょう。

[第1章　エディター拡張で使用するフォルダー](https://anchan828.github.io/editor-manual/web/part2-beginner.html) によりますと、Editorフォルダーが大事なようなので、Unity-SDK v0.13.0 のフォルダ内を眺めると… 確かにありますね。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/0af1f16f-de30-88b3-aec9-ff0a50bbc772.png)

[第8章　MenuItem](https://anchan828.github.io/editor-manual/web/part1-menuitem.html) あたりなど参考に、まずは ConfigEditor.cs を眺めてみましょう。192行目にそれっぽいコードがあります。

```cs:ConfigEditor.cs
[MenuItem("Watson/Configuration Editor", false, 0)]
private static void EditConfig()
{
    GetWindow<ConfigEditor>().Show();
}
```

うん、やはり。この ConfigEditor.cs ファイルで定義されている ConfigEditor クラスが、消えたConfiguration Editorの本体のようです。この中身をざっと眺めてみました。

# ConfigurationEditorクラスの役割

ConfigurationEditorクラスで GUI を作成する、OnGUI クラスの主要部分のコードを以下にまとめます。222行目あたりから。

まずは上部にあるサービスの一覧表示の部分のGUI作成コード。詳細は削っているので注意。

```cs:ConfigEditor.cs
private void OnGUI()
{
    Config cfg = Config.Instance;
    GUILayout.Label(m_WatsonIcon);  // 左上のWatsonアイコン

    if (GUILayout.Button("Register for Watson Services"))  // 最初のボタン
        Application.OpenURL(BLUEMIX_REGISTRATION);

    foreach (var setup in SERVICE_SETUP)
    {
        Config.CredentialInfo info = cfg.FindCredentials(setup.ServiceID);
        bool bValid = GetIsValid(setup);

        GUILayout.BeginHorizontal();  // 【サービスの行表示】 開始

        // 各サービスのステータスのアイコン表示
        if (m_ServiceStatus.ContainsKey(setup.ServiceID))
        {
            if (m_ServiceStatus[setup.ServiceID])
                GUILayout.Label(m_StatusUp, GUILayout.Width(20));
             else
                GUILayout.Label(m_StatusDown, GUILayout.Width(20));
        }
        else
            GUILayout.Label(m_StatusUnknown, GUILayout.Width(20));

        // 各サービスの説明テキスト
        GUILayout.Label(
            string.Format(
                "Service {0} {1}.",
                setup.ServiceName,
                bValid ? "CONFIGURED" : "NOT CONFIGURED"
            ),
            labelStyle
        );

        // 各サービスのアクションボタン
        if (GUILayout.Button("Configure", GUILayout.Width(100)))
            Application.OpenURL(setup.URL);
        if (bValid && GUILayout.Button("Clear", GUILayout.Width(100)))
            cfg.Credentials.Remove(info);

        GUILayout.EndHorizontal();  // 【サービスの行表示】 終了
    }
```

各サービスのCredential情報、つまり「資格情報」を読み込んでいます。ポイントとしては

* info = cfg.FindCredentials(setup.ServiceID) で資格情報を読み込む
* bValid = GetIsValid(setup) でサービスがきちんと設定されているか確認する

あたりで、ここらへんを追っていけば資格情報の格納先がわかりそうです。

ちなみに、ステータスのアイコン表示に使用されているリソースは以下です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/daf4896b-25ce-0470-f301-fe7ad215a562.png)

以下、引き続き OnGUI 関数の後半をみていきます。

```cs:ConfigEditor.cs
    GUILayout.Label("PASTE CREDENTIALS BELOW:");

    // Credential情報を貼り付けるテキストエリア
    m_PastedCredentials = EditorGUILayout.TextArea(m_PastedCredentials);

    // Credential情報を反映するApplyボタン
    GUI.SetNextControlName("Apply");
    if (GUILayout.Button("Apply Credentials"))
    {
        Config.CredentialInfo newInfo = new Config.CredentialInfo();
        if (newInfo.ParseJSON(m_PastedCredentials))
        {
            foreach (var setup in SERVICE_SETUP)
            {
                if (newInfo.m_URL.EndsWith(setup.ServiceAPI))
                {
                    newInfo.m_ServiceID = setup.ServiceID;
                    // 過去にCredential情報があれば削除する処理
                    cfg.Credentials.Add(newInfo);
                }
            }
        }
        // 成功もしくは失敗のダイアログを表示
        SaveConfig();
    }
}
```

こちらは「資格情報」の書き込みの処理ですが、重要なのはたぶん以下の3ポイントだと思われます。

* newInfo = new Config.CredentialInfo() で新しい資格情報を生成
* newInfo.ParseJSON 関数でJSON形式の資格情報を取り込み
* cfg.Credentials.Add(newInfo) で新しい資格情報を登録

資格情報に関しては、以下のオブジェクトが担っているのがわかります。

```cs:ConfigEditor.cs
Config cfg = Config.Instance;
```

このオブジェクトについて、もう少し見ていきましょう。

# Configクラスから探る

Config.cs ですが、Editor ではなく Utilities フォルダの中にありました。

まず読み出しのための FindCredentials関数を探してみると、217行目にあります。

```cs:Config.cs
public CredentialInfo FindCredentials(string serviceID)
{
  foreach (var info in m_Credentials)
    if (info.m_ServiceID == serviceID)
      return info;
  return null;
}
```

うん、単に探しているだけの関数ですね。そして肝心の資格情報の格納場所である m_Credentials は以下で設定されていました。

```cs:Config.cs
[fsProperty]
private List<CredentialInfo> m_Credentials = new List<CredentialInfo>();
public List<CredentialInfo> Credentials {
    get { return m_Credentials; }
    set { m_Credentials = value; }
}
```

うん、m_Credentials は単なるCredentialInfoのリストのようですね。すると上にある[fsProperty]属性が重要な気がします。

で、この属性がどこで定義してあるのかわからず悩んだのですが、結論から言ってこれ [FullSerializer](https://github.com/jacobdufault/fullserializer) という独立したライブラリでした。コードは ThirdParty フォルダの中にあります。

というわけで、ここでわかったのは 

* Config オブジェクトに資格情報が保存されている
* Config オブジェクトはFullSerializerというライブラリを用いてSerialize可能にされている

ですね。Configクラスはデータを抽象化したクラス(モデル)であり、アプリケーションに依存したロジックは無いことがわかります。

Serializeされた情報がどこに保存されるのか、Configクラスを眺めただけではわからない、ということになります。残念ですが、ひとつ前に戻って探し直しましょう。

# 再びConfigurationEditorクラス

そういえばConfiguration Editorの下のほうに「Save」というボタンがありますね。さきほどの OnGUI関数の最後のほう、その部分のコードを見てみましょう。

```cs:ConfigEditor.cs
    if (GUILayout.Button("Save"))
        SaveConfig();
```

おや？この SaveConfig って先ほどもありましたね。めっちゃ怪しい名前じゃないですか？そのコードを見てみると…

```cs:ConfigEditor.cs
private static void SaveConfig()
{
    if (!Directory.Exists(Application.streamingAssetsPath))
        Directory.CreateDirectory(Application.streamingAssetsPath);

    File.WriteAllText(
        Application.streamingAssetsPath + "/Config.json",
        Config.Instance.SaveConfig()
    );
    RESTConnector.FlushConnectors();
}
```

なるほど File.WriteAllText関数がありますので、思いっきりテキスト情報をファイルに書き込んでいますね。ココでしたか！そして使用されている streamingAssetsPath なんですが、Unityマニュアルにある [ストリーミングアセット](https://docs.unity3d.com/ja/current/Manual/StreamingAssets.html) ですよね。嫌な予感がします…

> Unity プロジェクトにおける StreamingAssets と呼ばれるフォルダーに配置したファイルはビルド先のプラットフォームの、特定のフォルダーにそのまま何も変換されない状態で保持されます。

ノーガード戦法キター！新しいバージョンでサポートされていないハズですよ、平文でIDやパスワード保持しちゃってた、ってことですよね？

[第3章　データの保存](https://anchan828.github.io/editor-manual/web/data_storage.html) にある [3.2　EditorUserSettings.Set/GetConfigValue](https://anchan828.github.io/editor-manual/web/data_storage.html) であれば、まだ暗号化されたものを…

# SpeechToText APIから逆に探る

なんとなく見えてきた気がしますが… 念のため逆から、信頼情報を利用するWatson API側も見ておきましょう。

[試してみる](https://qiita.com/yamachan360/items/911204e57957bd4bbef0) で最初に確認した SpeechToText API ですが、元のサンプルコードは

```cs:SampleSpeechToText.cs
private SpeechToText m_SpeechToText = new SpeechToText();
```

と引数無しでSpeechToTextインスタンスを生成していたわけです。ということは、その内部でConfig Editorが保存した「信頼情報」を参照しているハズ！ Services/SpeechToText フォルダにある SpeechToText.cs ファイルを見てコードを確認してみましょう。

以下、音声認識を実行する関数の先頭部分です。

```cs:SpeechToText.cs
public bool Recognize(AudioClip clip, OnRecognize callback)
{
  if (clip == null)
    throw new ArgumentNullException("clip");
  if (callback == null)
    throw new ArgumentNullException("callback");

  RESTConnector connector = RESTConnector.GetConnector(SERVICE_ID, "/v1/recognize");
  if (connector == null)
    return false;
```

RESTConnector.GetConnector のところが怪しい感じです。v2.2.1のほうは同じ部分が以下のようになっており、渡した Credentials をちゃんと利用しています。

```cs:SpeechToText.cs
    RESTConnector connector = RESTConnector.GetConnector(Credentials, "/v1/recognize");
    if (connector == null)
        return false;
```

やはりRESTConnectorが怪しい！というわけで、Connection フォルダにある RESTConnector.cs コードを追ってみましょう。

```cs:RESTConnector.cs
public static RESTConnector GetConnector(string serviceID, string function, bool useCache = true)
{
    RESTConnector connector = null;

    string connectorID = serviceID + function;
    if (useCache && sm_Connectors.TryGetValue(connectorID, out connector))
      return connector;

    Config cfg = Config.Instance;
    Config.CredentialInfo cred = cfg.FindCredentials(serviceID);
    if (cred == null)
    {
        Log.Error("Config", "Failed to find credentials for service {0}.", serviceID);
        return null;
    }
```

RESTConnectorクラスのGetConnectorメソッドの中にありましたね、FindCredentials(serviceID) と信頼情報をゲットしている部分が。こんな下のサービスレベルで信頼情報をやりとりしていたとは… で、この信頼情報を格納した Config オブジェクトは Instance メソッドにより

```cs:Config.cs
public static Config Instance {
   get { return Singleton<Config>.Instance; }
}
```

とシングルトンで得られるわけで。なるほどねぇ。そしてConfigクラスのコンストラクターが、例のConfig.jsonファイルを読みだしていることが確認できました。

```cs:Config.cs
public Config()
{
    LoadConfig();
}

public void LoadConfig()
{
    if (!Directory.Exists(Application.streamingAssetsPath))
        Directory.CreateDirectory(Application.streamingAssetsPath);
    LoadConfig(System.IO.File.ReadAllText(Application.streamingAssetsPath + Constants.Path.CONFIG_FILE));

}
public bool LoadConfig(string json)
{
    // 実際の処理は省略
}
```

いやぁ、なかなか長い確認の旅でしたw

# まとめ

Watson Unity-SDK にあったConfiguration Editorですが、信頼情報をConfiguration Editorに入力すると、内部でConfigオブジェクトに情報をまとめ、UnityのストリーミングアセットとしてConfig.jsonというファイルに保存してくれていた。

SpeechToTextなどのWatson APIのほう、サービス利用側は信頼情報など気にせずサービス用のオブジェクトを生成する。すると内部でRESTConnectorクラスがConfigオブジェクトを呼び出し、サービスの種類に応じた信頼情報を自動的に利用してくれていた。

以上、非常に便利な仕組みだが、たぶん欠点が2つあって…

* Unityのストリーミングアセットに(たぶん)平文としてIDやパスワードが保存されている
* サービスの種類から信頼情報を自動的にセットすることしかできない(明示的に指定できない)ので、サービスごとに1つの設定しか利用できない

という流れで、この仕組みはお蔵入りしたのではないか？と想像します。

# というわけで

Watson Unity-SDK にあったConfiguration Editorですが、[ストリーミングアセット](https://docs.unity3d.com/ja/current/Manual/StreamingAssets.html) に Config.json という大胆な名前でシリアライズ化した信頼情報を保存していた、というなかなか豪快な事実がわかりました。

正式っぽい v1.0.0 以降から Configuration Editor が消えた理由が、なんとなく想像できる気がしますね… そしてこのままの復活は難しいであろうことが予想できます。

あたらしい Unity-SDK がそのあたりを明確にガイドしてくれていれば良いのですが、いまのところは無さそうです。自分たちで考えなければいけないかもしれません。

というわけで対応策を考えたいのですが… 長くなりましたし、いったん落ち着きましたので、本日はこれで。もうすぐ GW なので、じっくり考えてまとめたいとおもいます！

GWにまとめました＞ [後編](https://qiita.com/yamachan360/items/a36c57e99a68597b8b0d) 

ではでは。