Example #1
0
        public static void OnProjectWindowItemOnGui(string guid, Rect rect)
        {
            if (!Globals <Config> .Value.MarkRed)
            {
                return;
            }

            var store = Globals <BacklinkStore> .Value;

            if (!store.Initialized)
            {
                return;
            }

            var path = AssetDatabase.GUIDToAssetPath(guid);

            ShowRowQuantity(rect, path, store);

            long size = 0;
            var  _    = store.UnusedFiles.TryGetValue(path, out size) || store.UnusedScenes.TryGetValue(path, out size);

            if (SearchUtils.IsUnused(path))
            {
                var buf = GUI.color;
                {
                    GUI.color = _style.RedHighlight;
                    GUI.Box(rect, string.Empty);
                }
                GUI.color = buf;
                GUI.Label(rect, CommonUtils.BytesToString(size), _style.ProjectViewCounterLabel);
            }
        }
        public void Run()
        {
            if (_from.IsEmpty())
            {
                return;
            }

            SearchArgMain.AllDestroy();
            ScenePaths.AllDestroy();
            FileResultRows.AllDestroy();
            SceneResultRows.AllDestroy();

            var wd = Globals <WindowData> .Value;

            if (wd.Window)
            {
                wd.Window.Repaint();
            }

            foreach (var i in _from.Out(out var get1, out _))
            {
                var t1 = get1[i];
                if (!t1.Target)
                {
                    continue;
                }
                wd.FindFrom = t1.From;

                try {
                    switch (t1.From)
                    {
                    case FindModeEnum.Scene:
                        World.NewEntityWith(out SearchArg st);
                        SearchUtils.Init(st, t1.Target, t1.Scene);
                        SearchUtils.InScene(st, t1.Scene);
                        break;

                    case FindModeEnum.File:
                        World.NewEntityWith(out SearchArg arg);
                        SearchUtils.Init(arg, t1.Target);
                        SearchUtils.FilesThatReference(arg);
                        SearchUtils.ScenesThatContain(t1.Target);
                        break;
                    }
                }
                catch (Exception e) {
                    Debug.LogException(e);
                }
            }

            _from.AllUnset <SelectionChanged>();
        }
        void RenderRows(WindowData windowData)
        {
            // todo show spinner until scene is loaded,

            if (FileResultRows.GetEntitiesCount() > 0)
            {
                windowData.ExpandFiles =
                    EditorGUILayout.Foldout(windowData.ExpandFiles,
                                            $"Usages in Project: {FileResultRows.GetEntitiesCount()}");
            }

            if (SearchArgMain.IsEmpty())
            {
                return;
            }

            if (windowData.ExpandFiles && windowData.FindFrom == FindModeEnum.File)
            {
                foreach (var i in FileResultRows.Out(out var get1, out var get2, out _, out _))
                {
                    DrawRowFile(get1[i], get2[i], windowData);
                }
            }


            var sceneMessage = $"Usages in Scenes: {ScenePaths.GetEntitiesCount()}";

            if (ScenePaths.GetEntitiesCount() > 0)
            {
                windowData.ExpandScenes =
                    EditorGUILayout.Foldout(windowData.ExpandScenes, sceneMessage);
            }


            if (!windowData.ExpandScenes)
            {
                return;
            }

            if (windowData.ExpandScenes && windowData.FindFrom == FindModeEnum.Scene)
            {
                foreach (var(grp, indices) in SceneResultRows.Out(out _, out var get2, out _, out _)
                         .GroupBy1(ResultComp.Instance))
                {
                    using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
                        var count = 0;
                        foreach (var i in indices)
                        {
                            if (count++ == 0)
                            {
                                if (GUILayout.Button(get2[i].Label, windowData.Style.RowMainAssetBtn))
                                {
                                    if (windowData.Click.IsDoubleClick(grp.RootGo))
                                    {
                                        // _selectionChangedByArrows = false;
                                        Selection.activeObject = grp.RootGo;
                                    }
                                    else
                                    {
                                        EditorGUIUtility.PingObject(grp.RootGo);
                                    }

                                    windowData.Click = new PrevClick(grp.RootGo);
                                }
                            }

                            DrawRowScene(get2[i]);
                        }
                    }
                }
            }

            using (new GUILayout.HorizontalScope()) {
                GUILayout.Space(windowData.Style.SceneIndent1);
                using (new EditorGUILayout.VerticalScope()) {
                    foreach (var i1 in ScenePaths.Out(out var get1, out var get2, out _))
                    {
                        windowData.SceneFoldout.text = get1[i1].PathNicified;
                        var details = get2[i1];

                        details.SearchRequested = details.Scene.isLoaded;
                        details.SearchRequested = EditorGUILayout.Foldout(details.SearchRequested,
                                                                          windowData.SceneFoldout, EditorStyles.foldout);
                        if (details.SearchRequested && details.Scene.isLoaded && !details.SearchDone)
                        {
                            var mainArg = SearchArgMain.GetSingle();

                            mainArg.Scene = SceneManager.GetSceneByPath(details.Scene.path);

                            SearchUtils.InScene(mainArg, details.Scene);
                            details.SearchDone = true;
                        }

                        if (!details.SearchRequested)
                        {
                            if (!details.Scene.isLoaded)
                            {
                                continue;
                            }
                            if (!details.WasOpened)
                            {
                                // to clean up on selection change
                                AufCtx.World.NewEntityWith(out SceneToClose comp);
                                comp.Scene      = details.Scene;
                                comp.ForceClose = true;
                            }

                            foreach (var row in SceneResultRows.Out(out _, out _, out var get3, out var entities))
                            {
                                if (!get3[row].ScenePath.Eq(details.Path))
                                {
                                    continue;
                                }

                                entities[row].Destroy();
                            }

                            details.SearchDone = false;
                        }
                        else
                        {
                            if (!details.Scene.isLoaded)
                            {
                                details.Scene = EditorSceneManager.OpenScene(details.Path, OpenSceneMode.Additive);

                                // to clean up on selection change
                                AufCtx.World.NewEntityWith(out SceneToClose comp);
                                comp.Scene       = details.Scene;
                                comp.SelectionId = Globals <PersistentUndoRedoState> .Value.Id;


#if UNITY_2019_1_OR_NEWER
                                EditorSceneManager.SetSceneCullingMask(details.Scene, 0);
#endif
                                details.SearchRequested = true;
                                // todo Scope component
#if later
                                if (details.Scene.isLoaded)
                                {
                                    EditorSceneManager.CloseScene(details.Scene, false);
                                }
#endif
                            }
                            else if (SceneResultRows.IsEmpty())
                            {
                                EditorGUILayout.LabelField("No in-scene dependencies found.");
                            }
                            else
                            {
                                using (new GUILayout.HorizontalScope()) {
                                    GUILayout.Space(windowData.Style.SceneIndent2);
                                    using (new EditorGUILayout.VerticalScope())
                                        foreach (var(grp, indices) in SceneResultRows
                                                 .Out(out var g1, out var g2, out var g3, out _)
                                                 .GroupBy1(ResultComp.Instance))
                                        {
                                            var any = false;
                                            foreach (var i3 in indices)
                                            {
                                                if (!g3[i3].ScenePath.Eq(details.Path))
                                                {
                                                    continue;
                                                }
                                                any = true;
                                                break;
                                            }

                                            if (!any)
                                            {
                                                continue;
                                            }

                                            using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
                                                var count = 0;
                                                foreach (var i2 in indices)
                                                {
                                                    if (!g3[i2].ScenePath.Eq(details.Path))
                                                    {
                                                        continue;
                                                    }

                                                    if (count++ == 0)
                                                    {
                                                        Result comp = g1[i2];
                                                        if (GUILayout.Button(g2[i2].Label,
                                                                             windowData.Style.RowMainAssetBtn))
                                                        {
                                                            if (windowData.Click.IsDoubleClick(grp.RootGo))
                                                            {
                                                                // _selectionChangedByArrows = false;
                                                                Selection.activeObject = comp.RootGo;
                                                            }
                                                            else
                                                            {
                                                                EditorGUIUtility.PingObject(comp.RootGo);
                                                            }

                                                            windowData.Click = new PrevClick(comp.RootGo);
                                                        }
                                                    }

                                                    DrawRowScene(g2[i2]);
                                                }
                                            }
                                        }
                                }
                            }
                        }
                    }
                }
            }
        }
        static bool AskDeleteUnusedScenes(SearchArg arg, List <string> unusedScenes, WindowData windowData)
        {
            if (arg == null || unusedScenes == null)
            {
                return(false);
            }
            var hasUnusedScenes = unusedScenes.Count > 0;
            var previous        = GUI.enabled;

            GUI.enabled = hasUnusedScenes;

            var guiContentRemoveScenes = windowData.Style.RemoveScene;

            if (GUILayout.Button(guiContentRemoveScenes,
                                 windowData.Style.RemoveUnusedBtn))
            {
                var choice = EditorUtility.DisplayDialog(
                    title: "Asset Cleaner",
                    message:
                    $"Do you really want to remove {unusedScenes.Count} scene(s)?",
                    ok: "Remove",
                    cancel: "Cancel");
                if (choice)
                {
                    EditorApplication.ExecuteMenuItem("File/Save Project");

                    var i     = 0f;
                    var total = (float)unusedScenes.Count;

                    foreach (var scene in unusedScenes)
                    {
                        var path = Application.dataPath.Replace("Assets", scene);
                        DeleteWithMeta(path);

                        if (total >= _progressBarShowFromLevel)
                        {
                            var percent = i * 100 / total;
                            if (Math.Abs(percent % 5f) < 0.01f)
                            {
                                if (EditorUtility.DisplayCancelableProgressBar(
                                        "Please wait...",
                                        "Deleting scenes...", percent))
                                {
                                    throw new Exception("Deleting aborted");
                                }
                            }

                            i++;
                        }
                    }

                    if (total >= _progressBarShowFromLevel)
                    {
                        EditorUtility.ClearProgressBar();
                    }

                    AssetDatabase.Refresh();
                    SearchUtils.Upd(arg);
                }

                GUI.enabled = previous;

                return(true);
            }

            GUI.enabled = previous;

            return(false);
        }
        BacklinkStore.UnusedQty ShowObjectName(BacklinkStore store, WindowData windowData, TargetTypeEnum targetTypeEnum, SearchArg arg, string[] selectedGuids)
        {
            float TextWidth()
            {
                _buf2.text = _contentBuf.text;
                return(20f + GUI.skin.button.CalcSize(_buf2).x);
            }

            if (arg == null || arg.Main == null)
            {
                return(new BacklinkStore.UnusedQty());
            }

            bool isMultiSelect = selectedGuids != null && selectedGuids.Length > 1;

            if (_contentBuf == null)
            {
                _contentBuf = new GUIContent {
                    tooltip = $"Click to ping"
                };
            }

            _contentBuf.image = isMultiSelect
                ? windowData.Style.MultiSelect.image
                : AssetPreview.GetMiniThumbnail(arg.Target);
            _contentBuf.text    = string.Empty;
            _contentBuf.tooltip = string.Empty;

            if (!isMultiSelect)
            {
                switch (targetTypeEnum)
                {
                case TargetTypeEnum.Directory:
                    if (!SearchArgMain.IsEmpty())
                    {
                        _contentBuf.text = $"{arg.Main.name} (Folder)";
                        if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
                                             GUILayout.MinWidth(TextWidth())))
                        {
                            EditorGUIUtility.PingObject(arg.Main);
                        }

                        if (AskDeleteUnusedFiles(arg, arg.UnusedAssetsFiltered, windowData))
                        {
                            return(new BacklinkStore.UnusedQty());
                        }

                        if (AskDeleteUnusedScenes(arg, arg.UnusedScenesFiltered, windowData))
                        {
                            return(new BacklinkStore.UnusedQty());
                        }
                    }

                    break;

                case TargetTypeEnum.File:
                    _contentBuf.text = $"{arg.Main.name} (File Asset)";
                    if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
                                         GUILayout.MinWidth(TextWidth())))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    bool hasUnusedFile = SearchUtils.IsUnused(arg.FilePath);
                    var  previous      = GUI.enabled;
                    GUI.enabled = hasUnusedFile;

                    if (GUILayout.Button(windowData.Style.RemoveFile,
                                         windowData.Style.RemoveUnusedBtn))
                    {
                        var choice = EditorUtility.DisplayDialog(
                            title: "Asset Cleaner",
                            message:
                            $"Do you really want to remove file: \"{arg.Main.name}\"?",
                            ok: "Remove",
                            cancel: "Cancel");
                        if (choice)
                        {
                            EditorApplication.ExecuteMenuItem("File/Save Project");
                            DeleteWithMeta(arg.FilePath);
                            AssetDatabase.Refresh();
                            SearchUtils.Upd(arg);
                        }
                    }

                    GUI.enabled = previous;

                    break;

                case TargetTypeEnum.Scene:
                    _contentBuf.text = $"{arg.Main.name} (Scene)";
                    if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
                                         GUILayout.MinWidth(TextWidth())))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    bool hasUnusedScene = SearchUtils.IsUnused(arg.FilePath);
                    previous    = GUI.enabled;
                    GUI.enabled = hasUnusedScene;

                    if (GUILayout.Button(windowData.Style.RemoveScene,
                                         windowData.Style.RemoveUnusedBtn))
                    {
                        var choice = EditorUtility.DisplayDialog(
                            title: "Asset Cleaner",
                            message:
                            $"Do you really want to remove scene: {arg.Main.name}?",
                            ok: "Remove",
                            cancel: "Cancel");
                        if (choice)
                        {
                            EditorApplication.ExecuteMenuItem("File/Save Project");
                            DeleteWithMeta(arg.FilePath);
                            AssetDatabase.Refresh();
                            SearchUtils.Upd(arg);
                        }
                    }

                    GUI.enabled = previous;
                    break;

                case TargetTypeEnum.ObjectInScene:
                    _contentBuf.text = $"{arg.Main.name} (Object in Scene)";

                    if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
                                         GUILayout.MinWidth(TextWidth())))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    break;

                case TargetTypeEnum.ObjectInStage:
                    _contentBuf.image = AssetPreview.GetMiniThumbnail(arg.Target);
                    _contentBuf.text  = $"{arg.Main.name} (Object in Staging)";

                    if (GUILayout.Button(_contentBuf,
                                         windowData.Style.RemoveUnusedBtn))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    break;

                default:
                    if (GUILayout.Button($"{arg.Main.name} (Unknown Object Type)",
                                         windowData.Style.RemoveUnusedBtn))
                    {
                        EditorGUIUtility.PingObject(arg.Main);
                    }

                    break;
                }
            }
            else
            {
                var unusedAssets = new List <string>();
                var unusedScenes = new List <string>();

                foreach (var guid in selectedGuids)
                {
                    var path = AssetDatabase.GUIDToAssetPath(guid);

                    if (store.UnusedFiles.TryGetValue(path, out _))
                    {
                        unusedAssets.Add(path);
                    }

                    else if (store.UnusedScenes.TryGetValue(path, out _))
                    {
                        unusedScenes.Add(path);
                    }

                    if (store.FoldersWithQty.TryGetValue(path, out _))
                    {
                        SearchArg searchArg = new SearchArg()
                        {
                            FilePath = path,
                            Target   = AssetDatabase.LoadAssetAtPath <DefaultAsset>(path),
                            Main     = AssetDatabase.LoadAssetAtPath <DefaultAsset>(path)
                        };
                        SearchUtils.Upd(searchArg);

                        foreach (var unusedAssetPath in searchArg.UnusedAssetsFiltered)
                        {
                            if (store.UnusedFiles.TryGetValue(unusedAssetPath, out _))
                            {
                                unusedAssets.Add(unusedAssetPath);
                            }
                        }

                        foreach (var unusedScenePath in searchArg.UnusedScenesFiltered)
                        {
                            if (store.UnusedScenes.TryGetValue(unusedScenePath, out _))
                            {
                                unusedScenes.Add(unusedScenePath);
                            }
                        }
                    }
                }

                unusedAssets = unusedAssets.Distinct().ToList();
                unusedScenes = unusedScenes.Distinct().ToList();

                var assetSize = unusedAssets.Sum(CommonUtils.Size);
                var sceneSize = unusedScenes.Sum(CommonUtils.Size);

                _contentBuf.text =
                    $"Assets: {unusedAssets.Count} ({CommonUtils.BytesToString(assetSize)}), Scenes: {unusedScenes.Count} ({CommonUtils.BytesToString(sceneSize)})";
                ;

                GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn, GUILayout.MinWidth(TextWidth()));

                if (AskDeleteUnusedFiles(arg, unusedAssets, windowData))
                {
                    return(new BacklinkStore.UnusedQty());
                }

                if (AskDeleteUnusedScenes(arg, unusedScenes, windowData))
                {
                    return(new BacklinkStore.UnusedQty());
                }

                return(new BacklinkStore.UnusedQty(unusedAssets.Count, unusedScenes.Count, assetSize + sceneSize));
            }

            return(new BacklinkStore.UnusedQty());
        }
        static bool TryGetHelpInfo(SearchArg arg, out string msg, out MessageType msgType)
        {
            msgType = MessageType.Info;
            if (arg == null)
            {
                msg = default;
                return(false);
            }

            var path = arg.FilePath;

            if (string.IsNullOrEmpty(path))
            {
                msg = default;
                return(false);
            }

            if (SearchUtils.IgnoredPaths(path, out var subPath))
            {
                msg = $"Paths containing '{subPath}' are ignored. You could add or remove it in Settings tab";
                return(true);
            }

            if (SearchUtils.IgnoredNonAssets(path) && !path.Eq("Assets"))
            {
                msg = $"Asset is outside of Assets folder";
                return(true);
            }

            if (IgnoreTypes.Check(path, out var type))
            {
                if (AssetDatabase.IsValidFolder(path))
                {
                    var scenes = arg.UnusedScenesFiltered?.Count;
                    var files  = arg.UnusedAssetsFiltered?.Count;
                    if (scenes == 0 && files == 0)
                    {
                        msg = default;
                        return(false);
                    }

                    var b = new StringBuilder();
                    b.Append("This directory contains: ");
                    var any = false;

                    if (files > 0)
                    {
                        any = true;

                        b.Append($"unused files ({files})");
                    }

                    if (scenes > 0)
                    {
                        if (any)
                        {
                            b.Append(", ");
                        }
                        b.Append($"unused scenes ({scenes})");
                    }

                    b.Append(
                        ".\nYou could delete them pressing corresponding button to the right.\nIf you don't want it to be inspected, please add it to Ignore list in the Settings tab");
                    msg = b.ToString();
                    return(true);
                }

                msg = $"Assets of type '{type.Name}' are ignored. Please contact support if you need to change it";
                return(true);
            }

            // if (Filters.ScenePaths.GetEntitiesCount() == 0 && Filters.FileResultRows.GetEntitiesCount() == 0) {
            if (SearchUtils.IsUnused(path))
            {
                type = AssetDatabase.GetMainAssetTypeAtPath(path);
                var name = type.IsAssignableFromInverse(typeof(SceneAsset)) ? "scene" : "file";
                msg =
                    $"This {name} has no explicit serialized usages and potentially could be removed. If you don't want it to be inspected, please add the containing folder to ignore list";
                return(true);
            }

            msgType = default;
            msg     = default;
            return(false);
        }