Space Ad Mobile 9.5.1
Space Ad Mobile makes ads easy to add into your mobile game: one unified workflow for LevelPlay, AdMob, Yandex.Ads across banners, interstitials and rewarded.
Loading...
Searching...
No Matches
AdAggregatorSelector.cs
1using System;
2using System.IO;
3using System.Linq;
4using UnityEditor;
5#if UNITY_2021_2_OR_NEWER
6using UnityEditor.Build;
7#endif
8using UnityEditor.Compilation;
9using UnityEngine;
10using Object = UnityEngine.Object;
11
12namespace SCG.SpaceAdMobile
13{
17 public partial class AdAggregatorSelector : EditorWindow
18 {
19 private const string StoreFreeVersionUrl = "https://assetstore.unity.com/packages/tools/integration/spacebanneradmobile-free-278681";
20 private const string StoreUrl = "https://assetstore.unity.com/packages/tools/integration/spaceadmobile-204424";
21 private const string DocsUrl = "https://space-cat.games/SpaceAdMobile/index.html";
22 private const int WindowMenuPriority = 2100;
23
24 private static bool firstRun;
25 private static AdAggregatorSelector window;
26 private static AdAggregatorResource adAggregatorResource;
27
28 private static AdMigrationWindow adMigrationWindow;
29
30 private static byte selectedAggregator;
31 private static Aggregator currentAggregator;
32
33 private Rect? lastPosition;
34 private bool suppressReopenOnDisable;
35 private bool isYandexUpdaterLaunching;
36
37 private const float InstalledInstallButtonExtraWidth = 8f;
38 private const float MinWidth = 520f;
39 private const float MinHeight = 400f;
40 private static readonly Vector2 s_minSize = new(MinWidth, MinHeight);
41 private static readonly Vector2 s_maxSize = new(MinWidth * 2f, MinHeight * 2f);
42
43 private static readonly GUIContent s_deleteOtherFoldersContent = new(
44 "Delete other ad folders",
45 "Remove folders belonging to other ad aggregators that are not currently selected.");
46
47 private static readonly GUIContent s_deleteDemosContent = new(
48 "Delete demos",
49 "Remove demo scenes and example scripts bundled with the selected aggregator SDK.");
50
51 private static readonly GUIContent s_deleteDocumentationContent = new(
52 "Delete documentation",
53 "Remove documentation files (PDF, TXT) shipped with the selected aggregator SDK.");
54
55 private static readonly GUIContent s_enhancedYandexDummiesContent = new(
56 "Enhanced Yandex editor dummies",
57 "Replace stock silent Yandex dummies with enhanced ones that fire ad-lifecycle events in the Editor.");
58
59 private static readonly GUIContent s_suppressYandexCs0414WarningsContent = new(
60 "Suppress CS0414",
61 "Add or remove warning code 414 in the selected Yandex SDK files that suppress warning 67.");
62
63 private static readonly GUIContent s_installContent = new("Install");
64 private static GUIContent UpdateContent { get; } = new(
65 "Update",
66 "Update the installed SDK to a newer version using .unitypackage files in your Downloads folder.");
67 private static GUIContent UpdateUnavailableContent { get; } = new(
68 "Update",
69 $"Python {YandexSdkUpdaterLauncher.MinimumPythonVersion} or newer was not found. Add python, python3, or the Windows Python Launcher to PATH.");
70 private static GUIContent UpdateCheckingContent { get; } = new(
71 "Update",
72 $"Checking for Python {YandexSdkUpdaterLauncher.MinimumPythonVersion} or newer.");
73 private static GUIContent UpdateStartingContent { get; } = new(
74 "Update",
75 "Waiting for the terminal and updater controller to start.");
76
80 [InitializeOnLoadMethod]
81 protected static void InitOnLoad() => EditorApplication.delayCall += OnLoad;
82
86 protected static void OnLoad()
87 {
88 EditorApplication.delayCall -= OnLoad;
89
90 if (Application.isPlaying) return;
91
92 adAggregatorResource = adAggregatorResource != null ? adAggregatorResource : AdAggregatorResource.Instance;
93 if (adAggregatorResource == null)
94 {
95 throw new UnityException($"The resource file '{nameof(AdAggregatorResource)}' is missing. Please reinstall the package.");
96 }
97
98 adAggregatorResource.DeleteOtherFolders = EditorPrefs.GetBool(AdAggregatorResource.KeyDeleteOtherFolders);
99 adAggregatorResource.DeleteDemo = EditorPrefs.GetBool(AdAggregatorResource.KeyDeleteDemo);
100 adAggregatorResource.DeleteDocumentation = EditorPrefs.GetBool(AdAggregatorResource.KeyDeleteDocumentation);
101 adAggregatorResource.UseEnhancedYandexEditorDummies =
103
105 {
106 ShowWindow();
107 }
108 }
109
113 protected static bool IsAggregatorSelected()
114 {
115 if (AssetDatabase.IsAssetImportWorkerProcess()) return true;
116 if (EditorApplication.isCompiling) return true;
117 if (adAggregatorResource == null) return false;
118
119#if UNITY_6000_3_OR_NEWER
120 var named = NamedBuildTarget.FromBuildTargetGroup(
121 BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget));
122 PlayerSettings.GetScriptingDefineSymbols(named, out var defines);
123#elif UNITY_2021_2_OR_NEWER
124 var named = NamedBuildTarget.FromBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
125 PlayerSettings.GetScriptingDefineSymbols(named, out var defines);
126#else
127 var definesLegacy = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
128 var defines = definesLegacy.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
129#endif
130 if (adMigrationWindow == null) // Change aggregator only if window not active
131 currentAggregator = adAggregatorResource.Aggregators
132 .FirstOrDefault(e => defines.Contains(e.Define, StringComparer.Ordinal));
133
134 var result = currentAggregator != null;
135 if (!result)
136 {
137 firstRun = true;
138 return false;
139 }
140
141 //firstRun = true; // Debug first run
142 firstRun = false;
143 selectedAggregator = (byte)Mathf.Max(0, Array.FindIndex(adAggregatorResource.Aggregators, e => defines.Contains(e.Define)));
144 return true;
145 }
146
150 [MenuItem("Window/" + nameof(SCG) + "/" + nameof(SpaceAdMobile) + ": Select Aggregator &S", priority = WindowMenuPriority)]
151 public static void ShowWindow()
152 {
153 EditorApplication.delayCall -= ShowWindow;
154
155 adAggregatorResource = adAggregatorResource != null ? adAggregatorResource : AdAggregatorResource.Instance;
156 YandexAssemblyUpdater.Validate0414WarningSuppressionPreference();
157
158 if (window == null)
159 {
160 window = GetWindow<AdAggregatorSelector>(true, "Select Ad Aggregator", true);
161 window.minSize = s_minSize;
162 window.maxSize = s_maxSize;
163 }
164 else
165 {
166 window.Focus();
167 }
168
169 YandexSdkUpdaterLauncher.RefreshPythonAvailability(() =>
170 {
171 if (window != null) window.Repaint();
172 });
173 }
174
178 protected virtual void OnEnable()
179 {
180 YandexAssemblyUpdater.Validate0414WarningSuppressionPreference();
181 YandexSdkUpdaterLauncher.RefreshPythonAvailability(Repaint);
182 }
183
187 protected virtual void OnGUI()
188 {
189 if (adAggregatorResource == null)
190 return;
191
192 if (firstRun)
193 {
194 EditorGUILayout.HelpBox(adAggregatorResource.WelcomeText, MessageType.Info);
195 }
196
197 using (new EditorGUI.DisabledScope(adMigrationWindow != null))
198 {
199 if (adMigrationWindow == null && lastPosition != null)
200 {
201 EditorApplication.delayCall += () =>
202 {
203 if (adMigrationWindow == null && lastPosition != null) ApplyLastPosition();
204 };
205 }
206
207 GUILayout.BeginHorizontal();
208 GUILayout.Label("Select an Ad Aggregator", EditorStyles.boldLabel);
209
210 if (adAggregatorResource.IsFreeVersion)
211 {
212 if (GUILayout.Button("Asset Store (Free)", GUILayout.MaxWidth(110)))
213 {
214 Application.OpenURL(StoreFreeVersionUrl);
215 }
216 }
217
218 if (GUILayout.Button("Asset Store", GUILayout.MaxWidth(200)))
219 {
220 Application.OpenURL(StoreUrl);
221 }
222
223 if (GUILayout.Button("Open Docs", GUILayout.MaxWidth(150)))
224 {
225 Application.OpenURL(DocsUrl);
226 }
227 GUILayout.EndHorizontal();
228
229 const int elementsInRow = 3;
230 var buttonWidth = EditorGUIUtility.currentViewWidth / elementsInRow - GUI.skin.button.padding.right + 2f;
231
232 for (byte aggrIndex = 0; aggrIndex < adAggregatorResource.Aggregators.Length; aggrIndex++)
233 {
234 var aggregatorObject = adAggregatorResource.Aggregators[aggrIndex];
235 if (aggregatorObject.Folder == null)
236 continue;
237
238 if (aggrIndex % elementsInRow == 0)
239 GUILayout.BeginHorizontal();
240
241 var buttonSkin = new GUIStyle(GUI.skin.button)
242 {
243 imagePosition = ImagePosition.ImageAbove
244 };
245
246 if (selectedAggregator == aggrIndex)
247 {
248 buttonSkin.active.textColor = Color.green;
249 buttonSkin.hover.textColor = Color.green;
250 buttonSkin.normal.textColor = Color.green;
251 }
252
253 var buttonContent = new GUIContent(aggregatorObject.Name, aggregatorObject.Texture);
254
255 if (GUILayout.Button(buttonContent, buttonSkin, GUILayout.Width(buttonWidth), GUILayout.Height(buttonWidth)))
256 {
257 selectedAggregator = aggrIndex;
258 }
259
260 if (aggrIndex % elementsInRow == elementsInRow - 1 || aggrIndex == adAggregatorResource.Aggregators.Length - 1)
261 {
262 GUILayout.EndHorizontal();
263 }
264 }
265
266 using (new EditorGUILayout.VerticalScope())
267 {
268 GUILayout.FlexibleSpace();
269
270 var toAggregator = GetSelectedAggregator();
271 var isYandexAggregator = IsYandexAggregator(toAggregator);
272 var shouldShowYandex0414Suppression = IsYandexAggregator(currentAggregator) && isYandexAggregator;
273 var shouldShowYandexFixButton = EditorUtility.scriptCompilationFailed && isYandexAggregator;
274
275#if !LEGACY_YANDEX_MOBILE_ADS
276 if (isYandexAggregator || shouldShowYandex0414Suppression)
277#else
278 if (shouldShowYandex0414Suppression)
279#endif
280 {
281 using (new EditorGUILayout.HorizontalScope())
282 {
283#if !LEGACY_YANDEX_MOBILE_ADS
284 if (isYandexAggregator)
285 {
286 adAggregatorResource.UseEnhancedYandexEditorDummies = GUILayout.Toggle(
287 adAggregatorResource.UseEnhancedYandexEditorDummies,
288 s_enhancedYandexDummiesContent);
289 }
290#endif
291
292 if (shouldShowYandex0414Suppression)
293 {
294 GUILayout.FlexibleSpace();
295 GUILayout.Label(s_suppressYandexCs0414WarningsContent, GUILayout.ExpandWidth(false));
296 var suppressYandexCs0414Warnings = YandexAssemblyUpdater.SuppressYandex0414Warnings;
297 var updatedSuppressYandexCs0414Warnings = GUILayout.Toggle(
298 suppressYandexCs0414Warnings,
299 GUIContent.none,
300 GUILayout.Width(EditorGUIUtility.singleLineHeight));
301 if (updatedSuppressYandexCs0414Warnings != suppressYandexCs0414Warnings)
302 YandexAssemblyUpdater.SuppressYandex0414Warnings = updatedSuppressYandexCs0414Warnings;
303 }
304 }
305 }
306
307 using (new EditorGUILayout.HorizontalScope())
308 {
309 adAggregatorResource.DeleteOtherFolders = GUILayout.Toggle(
310 adAggregatorResource.DeleteOtherFolders,
311 s_deleteOtherFoldersContent,
312 GUILayout.ExpandWidth(false));
313
314 GUILayout.FlexibleSpace();
315 GUILayout.Label(s_deleteDemosContent, GUILayout.ExpandWidth(false));
316 var deleteDemo = adAggregatorResource.DeleteDemo = GUILayout.Toggle(
317 adAggregatorResource.DeleteDemo,
318 GUIContent.none,
319 GUILayout.Width(EditorGUIUtility.singleLineHeight));
320 adAggregatorResource.DeleteDocumentation =
321 deleteDemo && EditorGUILayout.Toggle(s_deleteDocumentationContent, adAggregatorResource.DeleteDocumentation);
322 }
323
324 var isAggregatorSelectable = toAggregator != null &&
325 (currentAggregator == null ||
326 !string.Equals(currentAggregator.Name, toAggregator.Name, StringComparison.Ordinal));
327 var isAggregatorInstalled = toAggregator != null && IsAggregatorInstalled(toAggregator);
328 var shouldShowInstallOnly = isAggregatorSelectable && !isAggregatorInstalled;
329 var shouldShowInstall = shouldShowInstallOnly || isAggregatorInstalled;
330 var isInstallActionConfigured = !shouldShowInstall || IsInstallActionConfigured(toAggregator);
331
332 if (shouldShowInstallOnly && !isInstallActionConfigured)
333 {
334 EditorGUILayout.HelpBox(
335 $"Install action is not configured for '{toAggregator.Name ?? "Unknown"}'.",
336 MessageType.Warning);
337 }
338
339 using (new EditorGUILayout.HorizontalScope())
340 {
341#if UNITY_2022_3_OR_NEWER
342 var heigth = GUI.skin.button.lineHeight * 1.5f;
343#else
344 var heigth = GUI.skin.button.lineHeight * 2f;
345#endif
346
347 if (shouldShowInstallOnly)
348 {
349 using (new EditorGUI.DisabledScope(!isInstallActionConfigured))
350 {
351 if (GUILayout.Button(s_installContent, GUILayout.Height(heigth)))
352 {
353 OpenInstall(toAggregator);
354 }
355 }
356 }
357 else
358 {
359 if (shouldShowInstall)
360 {
361 using (new EditorGUI.DisabledScope(!isInstallActionConfigured))
362 {
363 var installWidth = GUI.skin.button.CalcSize(s_installContent).x + InstalledInstallButtonExtraWidth;
364 if (GUILayout.Button(s_installContent, GUILayout.Width(installWidth), GUILayout.Height(heigth)))
365 {
366 OpenInstall(toAggregator);
367 }
368 }
369 }
370
371 if (GUILayout.Button("OK", GUILayout.Height(heigth)))
372 {
373 if (currentAggregator != null && toAggregator != null &&
374 currentAggregator.Name != toAggregator.Name)
375 {
376 lastPosition = window.position;
377
378 window.maxSize = window.minSize;
379 window.position = new Rect(Vector2.zero, minSize);
380
381 EditorApplication.delayCall += () =>
382 {
383 adMigrationWindow = AdMigrationWindow.Open(adAggregatorResource,
384 currentAggregator,
385 toAggregator,
386 () => ApplyChanges(toAggregator),
387 dryRun: false,
388 modal: false);
389 };
390 }
391 else if (toAggregator != null)
392 {
393 ApplyChanges(toAggregator);
394 }
395 }
396 }
397
398 // ReSharper disable once InvertIf
399 if (shouldShowYandexFixButton && GUILayout.Button("Fix Yandex Assembly", GUILayout.Height(heigth)))
400 {
401 RunYandexUpdaterIfNeeded(toAggregator);
402 }
403
404 if (!IsYandexAggregator(currentAggregator) || !IsYandexAggregator(toAggregator)) return;
405 var pythonAvailabilityChecked = YandexSdkUpdaterLauncher.IsPythonAvailabilityChecked;
406 var updateAvailable = pythonAvailabilityChecked && YandexSdkUpdaterLauncher.IsPythonAvailable;
407 var updateContent = isYandexUpdaterLaunching
408 ? UpdateStartingContent
409 : !pythonAvailabilityChecked
410 ? UpdateCheckingContent
411 : updateAvailable ? UpdateContent : UpdateUnavailableContent;
412 var updateButtonWidth = GUI.skin.button.CalcSize(s_installContent).x + InstalledInstallButtonExtraWidth;
413 using (new EditorGUI.DisabledScope(!updateAvailable || isYandexUpdaterLaunching))
414 {
415 // ReSharper disable once InvertIf
416 if (GUILayout.Button(
417 updateContent,
418 GUILayout.Width(updateButtonWidth),
419 GUILayout.Height(heigth)))
420 {
421 isYandexUpdaterLaunching = true;
422 // ReSharper disable once InvertIf
423 if (!YandexSdkUpdaterLauncher.TryLaunch(HandleYandexUpdaterLaunchCompleted, out var error))
424 {
425 isYandexUpdaterLaunching = false;
426 EditorUtility.DisplayDialog("SDK Updater", error, "OK");
427 }
428 }
429 }
430 }
431 }
432 }
433 }
434
435 private void HandleYandexUpdaterLaunchCompleted(bool started, string error)
436 {
437 if (this == null || !isYandexUpdaterLaunching)
438 return;
439
440 isYandexUpdaterLaunching = false;
441 if (!started)
442 {
443 EditorUtility.DisplayDialog("SDK Updater", error ?? "The updater terminal could not be started.", "OK");
444 Repaint();
445 return;
446 }
447
448 suppressReopenOnDisable = true;
449 Close();
450 }
451
455 protected virtual void OnDisable()
456 {
457#if UNITY_EDITOR_OSX
458 if (isYandexUpdaterLaunching)
459 {
460 YandexSdkUpdaterLauncher.CancelPendingLaunch();
461 isYandexUpdaterLaunching = false;
462 }
463#endif
464
465 SaveEditorPreferences();
466
467 if (suppressReopenOnDisable || IsAggregatorSelected())
468 {
469 adAggregatorResource = null;
470 return;
471 }
472
473 Debug.LogWarning("Please select one of the ad aggregators.");
474 EditorApplication.delayCall += ShowWindow;
475 }
476
477 private void ApplyLastPosition()
478 {
479 if (lastPosition == null) return;
480 window.maxSize = s_maxSize;
481 window.position = lastPosition.Value;
482 lastPosition = null;
483 }
484
485 private void ApplyChanges(Aggregator aggregator)
486 {
487 if (aggregator == null)
488 return;
489
490 ApplyLastPosition();
491 ChangeDefine(aggregator.Define);
492 DeleteOtherAdFolders(!adAggregatorResource.DeleteDemo);
493 DeleteDemo(true);
494 RunYandexUpdaterIfNeeded(aggregator);
495 Close();
496 EditorApplication.delayCall += () =>
497 {
498 if (adMigrationWindow != null) adMigrationWindow.Close();
499 CompilationPipeline.RequestScriptCompilation();
500 };
501 }
502
503 private static bool IsYandexAggregator(Aggregator aggregator) =>
504 aggregator != null &&
505 string.Equals(aggregator.Define, DefineSymbolsManager.YandexMobileAds, StringComparison.Ordinal);
506
507 private static void RunYandexUpdaterIfNeeded(Aggregator aggregator)
508 {
509 if (!IsYandexAggregator(aggregator))
510 return;
511
512 SaveEditorPreferences();
513 YandexAssemblyUpdater.Update();
514 }
515
516 private static void SaveEditorPreferences()
517 {
518 if (adAggregatorResource == null)
519 return;
520
521 EditorPrefs.SetBool(AdAggregatorResource.KeyDeleteOtherFolders, adAggregatorResource.DeleteOtherFolders);
522 EditorPrefs.SetBool(AdAggregatorResource.KeyDeleteDemo, adAggregatorResource.DeleteDemo);
523 EditorPrefs.SetBool(AdAggregatorResource.KeyDeleteDocumentation, adAggregatorResource.DeleteDocumentation);
524 EditorPrefs.SetBool(AdAggregatorResource.KeyUseEnhancedYandexEditorDummies, adAggregatorResource.UseEnhancedYandexEditorDummies);
525 }
526
527 private static void ChangeDefine(string define)
528 {
529 for (byte aggrIndex = 0; aggrIndex < adAggregatorResource.Aggregators.Length; aggrIndex++)
530 {
531 if (aggrIndex == selectedAggregator)
532 DefineSymbolsManager.AddDefineSymbol(define);
533 else
534 DefineSymbolsManager.RemoveDefineSymbol(adAggregatorResource.Aggregators[aggrIndex].Define);
535 }
536 }
537
538 private static void DeleteOtherAdFolders(bool refresh)
539 {
540 if (!adAggregatorResource.DeleteOtherFolders) return;
541
542 for (byte aggrIndex = 0; aggrIndex < adAggregatorResource.Aggregators.Length; aggrIndex++)
543 {
544 if (aggrIndex == selectedAggregator)
545 continue;
546
547 var folderPath = GetAssetPath(adAggregatorResource.Aggregators[aggrIndex].Folder);
548 if (!IsProjectAssetsPath(folderPath) || !Directory.Exists(folderPath)) continue;
549
550 TryDeleteEntireFolder(folderPath);
551 }
552
553 if (refresh)
554 AssetDatabase.Refresh();
555 }
556
557 private static void DeleteDemo(bool refresh)
558 {
559 if (!adAggregatorResource.DeleteDemo) return;
560 var deleteDocumentation = adAggregatorResource.DeleteDocumentation;
561
562 const string pdf = ".pdf";
563 const string txt = ".txt";
564
565 var baseFolder = TryGetPackageRootPath(out var rootPath) ? rootPath : string.Empty;
566 var packageVersion = TryReadPackageVersion(baseFolder);
567
568 for (byte aggrIndex = 0; aggrIndex < adAggregatorResource.Aggregators.Length; aggrIndex++)
569 {
570 var folderPath = SampleLocator.GetDemoFolder(
571 baseFolder,
572 adAggregatorResource.Aggregators[aggrIndex].DemoFolderName,
573 packageVersion
574 );
575 if (!IsProjectAssetsPath(folderPath) || !Directory.Exists(folderPath)) continue;
576
577 var hasPdf = Directory.EnumerateFiles(folderPath, "*" + pdf, SearchOption.AllDirectories).Any();
578
579 if (deleteDocumentation || !hasPdf)
580 {
581 TryDeleteEntireFolder(folderPath);
582 }
583 else
584 {
585 DeleteFolderContentsExceptDocs(folderPath, pdf, txt);
586 }
587
588 if (!deleteDocumentation) continue;
589 folderPath = SampleLocator.GetDocsFolder(
590 baseFolder,
591 adAggregatorResource.Aggregators[aggrIndex].DemoFolderName,
592 packageVersion
593 );
594 if (!IsProjectAssetsPath(folderPath) || !Directory.Exists(folderPath)) continue;
595
596 TryDeleteEntireFolder(folderPath);
597 }
598
599 if (refresh)
600 AssetDatabase.Refresh();
601 }
602
603 private static bool TryGetPackageRootPath(out string rootPath)
604 {
605 rootPath = string.Empty;
606
607 if (adAggregatorResource == null)
608 return false;
609
610 var resourcePath = AssetDatabase.GetAssetPath(adAggregatorResource);
611 if (string.IsNullOrEmpty(resourcePath))
612 return false;
613
614 var current = Path.GetDirectoryName(resourcePath)?.Replace('\\', '/');
615 while (!string.IsNullOrEmpty(current))
616 {
617 var packageJsonPath = $"{current}/package.json";
618 if (AssetDatabase.LoadAssetAtPath<TextAsset>(packageJsonPath) != null)
619 {
620 rootPath = current;
621 return true;
622 }
623
624 var parent = Path.GetDirectoryName(current)?.Replace('\\', '/');
625 if (string.IsNullOrEmpty(parent) || parent == current)
626 break;
627
628 current = parent;
629 }
630
631 return false;
632 }
633
634 private static string TryReadPackageVersion(string baseFolder)
635 {
636 if (string.IsNullOrEmpty(baseFolder))
637 return string.Empty;
638
639 var packageJsonPath = $"{baseFolder}/package.json";
640 var packageAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(packageJsonPath);
641 if (packageAsset == null)
642 return string.Empty;
643
644 return ExtractPackageVersion(packageAsset.text);
645 }
646
647 private static string ExtractPackageVersion(string packageJson)
648 {
649 if (string.IsNullOrEmpty(packageJson))
650 return string.Empty;
651
652 const string key = "\"version\"";
653 var index = packageJson.IndexOf(key, StringComparison.Ordinal);
654 if (index < 0)
655 return string.Empty;
656
657 index = packageJson.IndexOf(':', index);
658 if (index < 0)
659 return string.Empty;
660
661 index++;
662 while (index < packageJson.Length && char.IsWhiteSpace(packageJson[index]))
663 index++;
664
665 if (index >= packageJson.Length || packageJson[index] != '"')
666 return string.Empty;
667
668 index++;
669 var end = packageJson.IndexOf('"', index);
670 return end < 0 ? string.Empty : packageJson[index..end].Trim();
671 }
672
673 private static string GetAssetPath(Object asset)
674 {
675 if (asset == null)
676 return string.Empty;
677
678 var unityPath = AssetDatabase.GetAssetPath(asset);
679 if (string.IsNullOrEmpty(unityPath))
680 return string.Empty;
681
682 var normalized = unityPath.Replace('\\', '/');
683
684 const string assetsPrefix = "Assets/";
685 if (normalized.StartsWith(assetsPrefix, StringComparison.Ordinal))
686 return Path.Combine(Application.dataPath, normalized[assetsPrefix.Length..]).Replace('\\', '/');
687
688 const string packagesPrefix = "Packages/";
689 if (normalized.StartsWith(packagesPrefix, StringComparison.Ordinal))
690 {
691 var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
692 return Path.Combine(projectRoot, normalized).Replace('\\', '/');
693 }
694
695 return normalized;
696 }
697
703 private static bool IsProjectAssetsPath(string absoluteOrUnityPath)
704 {
705 if (string.IsNullOrEmpty(absoluteOrUnityPath))
706 return false;
707
708 var normalized = absoluteOrUnityPath.Replace('\\', '/');
709
710 if (normalized.StartsWith("Packages/", StringComparison.Ordinal))
711 return false;
712
713 if (normalized.StartsWith("Assets/", StringComparison.Ordinal) ||
714 string.Equals(normalized, "Assets", StringComparison.Ordinal))
715 return true;
716
717 var dataPath = Application.dataPath.Replace('\\', '/');
718 return normalized.StartsWith(dataPath, StringComparison.OrdinalIgnoreCase);
719 }
720
721 #region Delete Folder Helpers
727 private static void TryDeleteEntireFolder(string folderPath)
728 {
729 try
730 {
731 if (Directory.Exists(folderPath))
732 {
733 Directory.Delete(folderPath, true);
734 }
735 }
736 catch { /* ignore */ }
737
738 try
739 {
740 var metaPath = folderPath.TrimEnd('/', '\\') + ".meta";
741 if (File.Exists(metaPath))
742 {
743 File.Delete(metaPath);
744 }
745 }
746 catch { /* ignore */ }
747 }
748
756 private static void DeleteFolderContentsExceptDocs(string rootFolder, params string[] extensions)
757 {
758 // Delete files except docs.
759 foreach (var file in Directory.EnumerateFiles(rootFolder, "*", SearchOption.AllDirectories))
760 {
761 var ext = Path.GetExtension(file);
762 var extensionEqual = extensions.Any(e => string.Equals(ext, e, StringComparison.OrdinalIgnoreCase));
763 if (extensionEqual) continue;
764
765 try
766 {
767 if (File.Exists(file))
768 {
769 File.Delete(file);
770 }
771
772 var metaPath = file + ".meta";
773 if (File.Exists(metaPath))
774 {
775 File.Delete(metaPath);
776 }
777 }
778 catch { /* ignore */ }
779 }
780
781 // Prune empty subdirectories (deepest first). Keep the root.
782 var allDirs = Directory.EnumerateDirectories(rootFolder, "*", SearchOption.AllDirectories)
783 .OrderByDescending(d => d.Length);
784
785 foreach (var dir in allDirs)
786 {
787 try
788 {
789 // If directory contains any entries, skip.
790 if (Directory.EnumerateFileSystemEntries(dir).Any()) continue;
791
792 Directory.Delete(dir, false);
793
794 var folderMeta = dir.TrimEnd('/', '\\') + ".meta";
795 if (File.Exists(folderMeta))
796 {
797 File.Delete(folderMeta);
798 }
799 }
800 catch { /* ignore */ }
801 }
802 }
803 #endregion
804 }
805}
const string KeyDeleteOtherFolders
Specifies the key delete other folders.
const string KeyDeleteDemo
Specifies the key delete demo.
const string KeyUseEnhancedYandexEditorDummies
Specifies the key use enhanced yandex editor dummies.
const string KeyDeleteDocumentation
Specifies the key delete documentation.
static AdAggregatorResource Instance
Singleton Resource instance.
bool DeleteDemo
Controls whether demo content is deleted during package preparation.
Provides ad aggregator resource functionality.
static void InitOnLoad()
Initializes the selector after the Unity Editor loads.
virtual void OnGUI()
Draws the Unity Editor window interface.
static bool IsAggregatorSelected()
Determines whether an ad aggregator is selected.
virtual void OnEnable()
Starts the Python availability check when Unity restores the selector after a domain reload.
virtual void OnDisable()
Handles the Unity disable callback.
static void ShowWindow()
Opens the ad aggregator selector window.
static void OnLoad()
Restores the selector state after the Unity Editor loads.
Provides ad aggregator selector functionality.
Provides aggregator functionality.
Represents a mobile ad manager component specifically designed for integration with Google AdMob serv...
Definition AdUnit.cs:7