/// <summary>
        /// 初期化処理を行う
        /// </summary>
        private void OnEnable()
        {
            dispList  = new List <UnityPackageInfo>();
            localPath = FileAccessor.GetLocalPackagePath();
            infoPath  = FileAccessor.GetSavePath();
            if (infoPath.Equals(""))
            {
                Debug.LogError("ERROR");
                return;
            }
            // ※tmpPathのフォルダは削除されるので変更する場合は注意してください
            tmpPath   = infoPath + "/tmp";
            noImage   = (Texture)AssetDatabase.LoadAssetAtPath("Assets/LocalPackageImporter/Editor/Images/noImage.png", typeof(Texture2D));
            heart_on  = (Texture)AssetDatabase.LoadAssetAtPath("Assets/LocalPackageImporter/Editor/Images/heart_on.png", typeof(Texture2D));
            heart_off = (Texture)AssetDatabase.LoadAssetAtPath("Assets/LocalPackageImporter/Editor/Images/heart_off.png", typeof(Texture2D));

            // unitypackageファイルのリストを取得する
            packagePathList = FileAccessor.GetPackageList(localPath);
            if (packagePathList == null)
            {
                // 不正なディレクトリの場合は終了
                DestroyImmediate(this);
            }
            // ローカルに持つ全unitypackage数
            allPackageNum = packagePathList.Count;

            // infoPathフォルダに保持しているunitypackage情報を事前に読み込んでおく
            ownedPackageInfoList = new List <UnityPackageInfo>();
            FileAccessor.LoadOwnedPackageInfo(ref ownedPackageInfoList, localPath, infoPath);
            SetDisplayPackageInfo();
            AssetDatabase.Refresh();
        }
        /// <summary>
        /// (各パッケージの)お気に入りボタンが押された場合
        /// </summary>
        /// <param name="index">押されたボタンのインデックス</param>
        private void PressedFavorite(int index)
        {
            UnityPackageInfo info = dispList[index];

            info.isFavorite = !info.isFavorite;
            dispList[index] = info;
            FileAccessor.UpdateFavoriteState(infoPath, dispList[index]);

            // ownedPackageInfoList更新(本来は該当する項目だけの更新としたほうがよい)
            FileAccessor.LoadOwnedPackageInfo(ref ownedPackageInfoList, localPath, infoPath);
            AssetDatabase.Refresh();
        }
        /// <summary>
        /// パッケージを検索する
        /// </summary>
        /// <param name="keyword">検索キーワード</param>
        /// <returns>検索キーワードを含むunitypackageのファイルリスト</returns>
        private List <string> SearchPackage(string keyword)
        {
            List <string> allList  = FileAccessor.GetPackageList(localPath);
            List <string> pathList = new List <string>();

            for (int i = 0; i < allList.Count; ++i)
            {
                string fileNameNoExt = Path.GetFileNameWithoutExtension(allList[i]);
                if (fileNameNoExt.ToLower().Contains(keyword.ToLower()))
                {
                    pathList.Add(allList[i]);
                }
            }
            return(pathList);
        }
        /// <summary>
        /// unitypackageのメタデータを取得・アップデートします
        /// </summary>
        private void UpdateMetadata()
        {
            // 実行時間計測
            // System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();

            FileAccessor.ExtractUnityPackageInfo(localPath, infoPath);
            FileAccessor.ExtractThumbnailsFromPackage(localPath, infoPath, tmpPath);
            FileAccessor.LoadOwnedPackageInfo(ref ownedPackageInfoList, localPath, infoPath);
            SetDisplayPackageInfo();
            AssetDatabase.Refresh();

            // watch.Stop();
            // float ms = watch.ElapsedMilliseconds;
            // Debug.LogFormat("{0} ms", ms);
        }
        /// <summary>
        /// 保持しているunitypackage情報を全て読み込む
        /// </summary>
        /// <param name="ownedPackageInfoList">保持しているパッケージ情報リスト</param>
        /// <param name="packagePath">unitypackageのパス</param>
        /// <param name="infoPath">パッケージ情報を格納しているフォルダのパス</param>
        public static void LoadOwnedPackageInfo(ref List <UnityPackageInfo> ownedPackageInfoList, string packagePath, string infoPath)
        {
            ownedPackageInfoList.Clear();
            List <string> allList = FileAccessor.GetPackageList(packagePath);

            foreach (var path in allList)
            {
                string           fileNameNoExt = Path.GetFileNameWithoutExtension(path);
                UnityPackageInfo info          = new UnityPackageInfo();
                info.name = fileNameNoExt;
                string savePath = infoPath + "/" + fileNameNoExt;
                CreateDirectoryIfNotFound(savePath);
                if (!File.Exists(savePath + "/icon.png"))
                {
                    info.thumb = null;
                }
                else
                {
                    Texture2D tex = new Texture2D(1, 1);
                    tex.LoadImage(File.ReadAllBytes(savePath + "/icon.png"));
                    info.thumb = tex;
                }
                JsonData json = GetJsonData(path, infoPath);
                if (json == null)
                {
                    info.id      = null;
                    info.version = null;
                }
                else
                {
                    info.id      = json.id;
                    info.version = json.version;
                }
                info.size       = GetPackageSize(path);
                info.isFavorite = GetFavoriteState(infoPath, fileNameNoExt);
                ownedPackageInfoList.Add(info);
            }
        }
        /// <summary>
        /// お気に入りのパッケージを表示する(検索キーワードが入力されている場合も考慮)
        /// </summary>
        /// <returns>表示するパッケージのフルパスリスト</returns>
        private List <string> GetFavoritePackage()
        {
            List <string> allList  = FileAccessor.GetPackageList(localPath);
            List <string> pathList = new List <string>();

            for (int i = 0; i < allList.Count; ++i)
            {
                string fileNameNoExt = Path.GetFileNameWithoutExtension(allList[i]);
                for (int j = 0; j < packagePathList.Count; ++j)
                {
                    // 検索後のパッケージパスリスト
                    string afterFileNameNoExt = Path.GetFileNameWithoutExtension(packagePathList[j]);

                    // 所有しているパッケージの名称と一致し、かつお気に入り、かつ検索後のパッケージパスと一致する場合
                    if (ownedPackageInfoList[i].name == fileNameNoExt &&
                        ownedPackageInfoList[i].isFavorite &&
                        ownedPackageInfoList[i].name == afterFileNameNoExt)
                    {
                        pathList.Add(allList[i]);
                    }
                }
            }
            return(pathList);
        }
        /// <summary>
        /// unitypackageのバイナリデータからパッケージ情報を取得しjsonファイルに保存する
        /// <param name="packagePath">unitypackageのパス</param>
        /// <param name="infoPath">パッケージ情報を格納しているフォルダのパス</param>
        /// </summary>
        public static void ExtractUnityPackageInfo(string packagePath, string infoPath)
        {
            List <string> allList = FileAccessor.GetPackageList(packagePath);

            for (int i = 0; i < allList.Count; ++i)
            {
                // unitypackageファイルを開く
                using (FileStream fs = new FileStream(allList[i], FileMode.Open, FileAccess.Read))
                {
                    // マジックナンバー
                    byte[] magicNum = new byte[2];
                    // フラグ(3bit目が1の場合拡張フィールドが存在する)
                    byte[] flag = new byte[1];
                    // 拡張フィールドのサイズ
                    byte[] extSize = new byte[2];

                    // 現在のFileStreamの位置を保存
                    long fpos = fs.Position;

                    // マジックナンバーの読み込み
                    fs.Read(magicNum, 0, 2);
                    //Debug.Log("magicNum:" + BitConverter.ToString(magicNum).Replace("-", " "));
                    // マジックナンバーの確認
                    if (magicNum[0] != 0x1F || magicNum[1] != 0x8B)
                    {
                        Debug.LogWarning("Invalid unitypackage file.");
                        continue;
                    }

                    // FileStreamが指す位置を4バイト目に移動する
                    fpos = fs.Seek(3, SeekOrigin.Begin);
                    // フラグの読み込み
                    fs.Read(flag, 0, 1);
                    //Debug.Log("flag:" + BitConverter.ToString(flag) + "(16進数), " + Convert.ToString(flag[0], 2) + "(2進数)");
                    // 3bit目が1になっているか(拡張フィールドが存在するか)確認
                    if (((flag[0] & 0x04) >> 2) != 1)
                    {
                        Debug.LogWarning("Extention field not found.");
                        continue;
                    }

                    // FileStreamが指す位置を11バイト目に移動する
                    fpos = fs.Seek(10, SeekOrigin.Begin);
                    // 拡張フィールドのサイズの読み込み
                    fs.Read(extSize, 0, 2);
                    int size = BitConverter.ToInt16(extSize, 0);
                    //Debug.Log("extSize:" + BitConverter.ToString(extSize).Replace("-", " ") + "(16進数), " + size + "(10進数)");
                    byte[] extField = new byte[size];

                    // 拡張フィールドを読み込む
                    fs.Read(extField, 0, size);
                    //Debug.Log("extField:" + BitConverter.ToString(extField).Replace("-", " "));
                    //string str = System.Text.Encoding.UTF8.GetString(extField);
                    //Debug.Log("extField(text):" + str);

                    // 保存先のディレクトリが存在しない場合は作成する
                    string fileNameNoExt = Path.GetFileNameWithoutExtension(allList[i]);
                    string dir           = infoPath + "/" + fileNameNoExt;
                    CreateDirectoryIfNotFound(dir);

                    // 拡張フィールドの内容をファイルに書き込む
                    using (FileStream outFs = new FileStream(dir + "/info.json", FileMode.Create, FileAccess.Write))
                    {
                        // 4バイト不明なデータが入っているのでoffsetを4としている
                        int offset = 4;
                        outFs.Write(extField, offset, extField.Length - offset);
                    }
                }
            }
        }
        /// <summary>
        /// unitypackageからサムネイルを取得しinfoPathフォルダ配下に保存する
        /// (注意:unitypackageを解凍してサムネイルを取り出すので時間がかかります)
        /// </summary>
        /// <param name="packagePath">unitypackageのパス</param>
        /// <param name="infoPath">パッケージ情報を格納するフォルダのパス</param>
        /// <param name="tmpPath">一時フォルダのパス</param>
        public static void ExtractThumbnailsFromPackage(string packagePath, string infoPath, string tmpPath)
        {
            List <string> allList = FileAccessor.GetPackageList(packagePath);

            for (int i = 0; i < allList.Count; ++i)
            {
                float progress = (float)(i + 1) / (float)allList.Count;
                EditorUtility.DisplayProgressBar("Getting unitypackage information", (i + 1).ToString() + "/" + allList.Count.ToString(), progress);

                // ファイル名から拡張子をのぞいた文字列を取得
                string fileNameNoExt = Path.GetFileNameWithoutExtension(allList[i]);
                // サムネイル保存先パス
                string thumbDir = infoPath + "/" + fileNameNoExt;

                // 既にアイコンファイルが存在する場合
                if (File.Exists(thumbDir + "/icon.png"))
                {
                    continue;
                }

                try
                {
                    // unitypackage解凍先パス
                    string tmpDir = tmpPath + "/" + fileNameNoExt;
                    CreateDirectoryIfNotFound(tmpDir);

                    //unitypackage(tar.gz)を読み取り専用で開く
                    using (var tgzStream = File.OpenRead(allList[i]))
                    {
                        //GZipStreamオブジェクトを解凍で生成
                        using (var gzStream = new GZipStream(tgzStream, CompressionMode.Decompress))
                        {
                            //指定したディレクトリにtarを展開
                            ExtractTarByEntry(gzStream, tmpDir, false);
                        }
                    }

                    // サムネイルをinfoPath配下にコピー
                    CreateDirectoryIfNotFound(thumbDir);
                    // unitypackage内にアイコンがない場合はスキップ
                    if (!File.Exists(tmpDir + "/.icon.png"))
                    {
                        continue;
                    }
                    File.Copy(tmpDir + "/.icon.png", thumbDir + "/icon.png", true);

                    // 解凍したパッケージフォルダを削除
                    Directory.Delete(tmpDir, true);
                }
                catch (Exception e)
                {
                    Debug.LogWarning(e.ToString());
                    EditorUtility.ClearProgressBar();
                }
            }

            // tmpディレクトリを削除
            try
            {
                if (Directory.Exists(tmpPath))
                {
                    Directory.Delete(tmpPath, true);
                }
                EditorUtility.ClearProgressBar();
            }
            catch (Exception e)
            {
                Debug.LogWarning(e.ToString());
                EditorUtility.ClearProgressBar();
            }
        }
        /// <summary>
        /// GUIを描画する
        /// </summary>
        private void OnGUI()
        {
            if (packagePathList == null)
            {
                packagePathList = FileAccessor.GetPackageList(localPath);
                SetDisplayPackageInfo();
            }
            EditorGUILayout.Space();

            // GUIになんらかの変更が行われた時、EndChangeCheckがtrueを返す
            EditorGUI.BeginChangeCheck();
            {
                EditorGUILayout.BeginHorizontal();
                {
                    // ハートトグルボタンのスタイル設定
                    if (heartToggleStyle == null)
                    {
                        heartToggleStyle         = new GUIStyle(GUI.skin.button);
                        heartToggleStyle.margin  = new RectOffset(6, 0, 0, 0);
                        heartToggleStyle.padding = new RectOffset(0, 0, 0, 0);
                    }
                    heartToggle = GUILayout.Toggle(heartToggle, heartToggle ? heart_on : heart_off, heartToggleStyle, GUILayout.Width(20), GUILayout.Height(20));

                    // 検索
                    searchWord = GUILayout.TextField(searchWord, GUILayout.Width(searchWidth));
                    string count = "(" + packagePathList.Count + "/" + allPackageNum + ")";
                    GUILayout.Label("Search" + count, EditorStyles.boldLabel);

                    if (GUILayout.Button("Update metadata"))
                    {
                        UpdateMetadata();
                    }
                }
                EditorGUILayout.EndHorizontal();
            }
            if (EditorGUI.EndChangeCheck())
            {
                if (searchWord != "")
                {
                    // 検索
                    packagePathList = SearchPackage(searchWord);
                }
                else
                {
                    // 空白のときは全てのパッケージを表示する
                    packagePathList = FileAccessor.GetPackageList(localPath);
                }

                if (heartToggle)
                {
                    // ハートがついたもののみ表示する
                    packagePathList = GetFavoritePackage();
                }

                SetDisplayPackageInfo();
                AssetDatabase.Refresh();
            }
            EditorGUILayout.Space();

            var scrollArea = EditorGUILayout.BeginHorizontal();

            {
                // スクロールビュー
                scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUI.skin.box);
                {
                    // 一項目分の高さ
                    int lineHeight = 74;

                    // 上部の描画が不要なエリアをスペースで埋める
                    var startIndex = (int)(scrollPos.y / lineHeight);
                    GUILayout.Space(startIndex * lineHeight);

                    var listCount = packagePathList.Count;
                    var endIndex  = listCount;
                    if (rootHeight > 0f)
                    {
                        // 空白が下部に表示されないようにするためにこのカウント分追加で描画する
                        int marginCount = 3;
                        endIndex = startIndex + (int)(rootHeight / lineHeight) + marginCount;
                        if (endIndex > listCount)
                        {
                            endIndex = listCount;
                        }
                    }

                    for (int i = startIndex; i < endIndex; ++i)
                    {
                        string path          = packagePathList[i];
                        string fileNameNoExt = Path.GetFileNameWithoutExtension(path);

                        EditorGUILayout.BeginHorizontal(GUI.skin.box);
                        {
                            EditorGUILayout.BeginVertical(GUILayout.Width(buttonWidth));
                            {
                                if (GUILayout.Button("Import"))
                                {
                                    AssetDatabase.ImportPackage(path, true);
                                }

                                bool disable = false;
                                if (dispList[i].id == null)
                                {
                                    disable = true;
                                }
                                EditorGUI.BeginDisabledGroup(disable);
                                {
                                    if (GUILayout.Button("Asset Store"))
                                    {
                                        AssetStore.Open("/content/" + dispList[i].id);

                                        // 外部ブラウザで開く場合
                                        //Application.OpenURL("https://www.assetstore.unity3d.com/jp/#!/content/" + dispList[i].id);
                                    }
                                }
                                EditorGUI.EndDisabledGroup();

                                // ハートボタンのスタイル設定
                                if (heartButtonStyle == null)
                                {
                                    heartButtonStyle        = new GUIStyle(GUI.skin.label);
                                    heartButtonStyle.margin = new RectOffset(32, 0, 0, 0);
                                }
                                if (GUILayout.Button(dispList[i].isFavorite ? heart_on : heart_off, heartButtonStyle))
                                {
                                    PressedFavorite(i);
                                }
                            }
                            EditorGUILayout.EndVertical();

                            // サムネイルのスタイル設定
                            if (thumbStyle == null)
                            {
                                thumbStyle         = new GUIStyle(GUI.skin.box);
                                thumbStyle.margin  = new RectOffset(0, 0, 0, 0);
                                thumbStyle.padding = new RectOffset(0, 0, 0, 0);
                            }
                            if (dispList[i].thumb)
                            {
                                GUILayout.Button(dispList[i].thumb, thumbStyle, GUILayout.Width(thumbWitdh), GUILayout.Height(thumbHeight));
                            }
                            else
                            {
                                GUILayout.Button(noImage, thumbStyle, GUILayout.Width(thumbWitdh), GUILayout.Height(thumbHeight));
                            }

                            EditorGUILayout.BeginVertical();
                            {
                                GUILayout.Label(fileNameNoExt);
                                GUILayout.Label("Size: " + dispList[i].size);
                                GUILayout.Label("Version: " + dispList[i].version);
                            }
                            EditorGUILayout.EndVertical();
                        }
                        EditorGUILayout.EndHorizontal();
                    }

                    // 下部の描画が不要なエリアをスペースで埋める
                    GUILayout.Space((listCount - endIndex) * lineHeight);
                }
                EditorGUILayout.EndScrollView();
            }
            EditorGUILayout.EndHorizontal();

            // スクロールエリアの描画が完了したタイミングで更新
            if (scrollArea.height > 0f)
            {
                rootHeight = scrollArea.height;
            }

            /*
             * // for DEBUG
             * GUILayout.Label("ScrollArea:" + rootHeight);
             * GUILayout.Label("Window Size : (" + Screen.width.ToString() + ", " + Screen.height.ToString() + ")", EditorStyles.boldLabel);
             * Handles.color = Color.red;
             * Handles.DrawLine(new Vector2(0, 0), new Vector2(100, 100));
             */
        }