A game about forced loneliness, made by TACStudios
1using System;
2using System.IO;
3using System.Linq;
4
5using UnityEditor;
6using UnityEngine;
7
8using Codice.Client.BaseCommands;
9using Codice.Client.Common;
10using Codice.Client.Common.Connection;
11using Codice.Client.Common.Encryption;
12using Codice.Client.Common.EventTracking;
13using Codice.Client.Common.FsNodeReaders;
14using Codice.Client.Common.FsNodeReaders.Watcher;
15using Codice.Client.Common.Threading;
16using Codice.Client.Common.WebApi;
17using Codice.CM.Common;
18using Codice.CM.ConfigureHelper;
19using Codice.CM.WorkspaceServer;
20using Codice.LogWrapper;
21using Codice.Utils;
22using CodiceApp.EventTracking;
23using MacUI;
24using PlasticGui;
25using PlasticPipe.Certificates;
26using Unity.PlasticSCM.Editor.Configuration;
27using Unity.PlasticSCM.Editor.Tool;
28using Unity.PlasticSCM.Editor.UI;
29
30namespace Unity.PlasticSCM.Editor
31{
32 internal static class PlasticApp
33 {
34 internal static ILog GetLogger(string name)
35 {
36 if (!mIsDomainUnloadHandlerRegistered)
37 {
38 // Register the Domain Unload Handler before the LogManager is initialized,
39 // so the domain unload handler for the app is processed before the log manager one,
40 // and thus the AppDomainUnload is printed in the log
41 RegisterDomainUnloadHandler();
42 mIsDomainUnloadHandlerRegistered = true;
43 }
44
45 return LogManager.GetLogger(name);
46 }
47
48 internal static bool HasRunningOperation()
49 {
50 if (mWorkspaceWindow != null &&
51 mWorkspaceWindow.IsOperationInProgress())
52 return true;
53
54 if (mWkInfo == null)
55 return false;
56
57 return TransactionManager.Get().ExistsAnyWorkspaceTransaction(mWkInfo);
58 }
59
60 internal static void InitializeIfNeeded()
61 {
62 if (mIsInitialized)
63 return;
64
65 mIsInitialized = true;
66
67 // Configure logging on initialize to avoid adding the performance cost of it
68 // on every Editor load and Domain reload for non-UVCS users.
69 ConfigureLogging();
70
71 mLog.Debug("InitializeIfNeeded");
72
73 mLog.DebugFormat("Unity version: {0}", Application.unityVersion);
74
75 // Ensures that the Edition Token is initialized from the UVCS installation regardless of if the PlasticWindow is opened
76 UnityConfigurationChecker.SynchronizeUnityEditionToken();
77 PlasticInstallPath.LogInstallationInfo();
78
79 if (!PlasticPlugin.IsUnitTesting)
80 GuiMessage.Initialize(new UnityPlasticGuiMessage());
81
82 RegisterExceptionHandlers();
83 RegisterBeforeAssemblyReloadHandler();
84 RegisterEditorWantsToQuit();
85 RegisterEditorQuitting();
86
87 InitLocalization();
88
89 if (!PlasticPlugin.IsUnitTesting)
90 ThreadWaiter.Initialize(new UnityThreadWaiterBuilder());
91
92 ServicePointConfigurator.ConfigureServicePoint();
93 CertificateUi.RegisterHandler(new ChannelCertificateUiImpl());
94
95 SetupFsWatcher();
96
97 EditionManager.Get().DisableCapability(EnumEditionCapabilities.Extensions);
98
99 ClientHandlers.Register();
100
101 PlasticGuiConfig.SetConfigFile(PlasticGuiConfig.UNITY_GUI_CONFIG_FILE);
102
103 if (!PlasticPlugin.IsUnitTesting)
104 {
105 mEventSenderScheduler = EventTracking.Configure(
106 (PlasticWebRestApi)PlasticGui.Plastic.WebRestAPI,
107 ApplicationIdentifier.UnityPackage,
108 IdentifyEventPlatform.Get());
109 }
110
111 UVCPackageVersion.Initialize();
112
113 if (mEventSenderScheduler != null)
114 {
115 mPingEventLoop = new PingEventLoop(
116 BuildGetEventExtraInfoFunction.ForPingEvent());
117 mPingEventLoop.Start();
118 }
119
120 PlasticMethodExceptionHandling.InitializeAskCredentialsUi(
121 new CredentialsUiImpl());
122 ClientEncryptionServiceProvider.SetEncryptionPasswordProvider(
123 new MissingEncryptionPasswordPromptHandler());
124 }
125
126 internal static void Enable()
127 {
128 PlasticGui.Plastic.WebRestAPI.SetToken(
129 CmConnection.Get().BuildWebApiTokenForCloudEditionDefaultUser());
130
131 if (!PlasticPlugin.IsUnitTesting)
132 SetupCloudOrganizations(PlasticGui.Plastic.WebRestAPI);
133 }
134
135 internal static void SetWorkspace(WorkspaceInfo wkInfo)
136 {
137 mWkInfo = wkInfo;
138
139 RegisterApplicationFocusHandlers();
140
141 if (mEventSenderScheduler == null)
142 return;
143
144 mPingEventLoop.SetWorkspace(mWkInfo);
145 }
146
147 internal static void RegisterWorkspaceWindow(IWorkspaceWindow workspaceWindow)
148 {
149 mWorkspaceWindow = workspaceWindow;
150 }
151
152 internal static void UnRegisterWorkspaceWindow()
153 {
154 mWorkspaceWindow = null;
155 }
156
157 internal static void EnableMonoFsWatcherIfNeeded()
158 {
159 if (PlatformIdentifier.IsMac())
160 return;
161
162 MonoFileSystemWatcher.IsEnabled = true;
163 }
164
165 internal static void DisableMonoFsWatcherIfNeeded()
166 {
167 if (PlatformIdentifier.IsMac())
168 return;
169
170 MonoFileSystemWatcher.IsEnabled = false;
171 }
172
173 internal static void Dispose()
174 {
175 if (!mIsInitialized)
176 return;
177
178 try
179 {
180 mLog.Debug("Dispose");
181
182 UnRegisterExceptionHandlers();
183 UnRegisterApplicationFocusHandlers();
184 UnRegisterEditorWantsToQuit();
185 UnRegisterEditorQuitting();
186
187 if (mEventSenderScheduler != null)
188 {
189 mPingEventLoop.Stop();
190 // Launching and forgetting to avoid a timeout when sending events files and no
191 // network connection is available.
192 // This will be refactored once a better mechanism to send event is in place
193 mEventSenderScheduler.EndAndSendEventsAsync();
194 }
195
196 if (mWkInfo == null)
197 return;
198
199 WorkspaceFsNodeReaderCachesCleaner.CleanWorkspaceFsNodeReader(mWkInfo);
200 }
201 finally
202 {
203 mIsInitialized = false;
204 }
205 }
206
207 static void SetupCloudOrganizations(IPlasticWebRestApi restApi)
208 {
209 if (!EditionToken.IsCloudEdition())
210 return;
211
212 // The plastic library holds an internal cache of slugs that relies on the file unityorgs.conf.
213 // This file might contain outdated information or not exist at all, so we need to ensure
214 // the cloud organizations are loaded and populated to the internal cache during the initialization.
215 OrganizationsInformation.LoadCloudOrganizationsAsync(restApi);
216 }
217
218 static void RegisterDomainUnloadHandler()
219 {
220 AppDomain.CurrentDomain.DomainUnload += AppDomainUnload;
221 }
222
223 static void RegisterEditorWantsToQuit()
224 {
225 EditorApplication.wantsToQuit += OnEditorWantsToQuit;
226 }
227
228 static void RegisterEditorQuitting()
229 {
230 EditorApplication.quitting += OnEditorQuitting;
231 }
232
233 static void RegisterBeforeAssemblyReloadHandler()
234 {
235 AssemblyReloadEvents.beforeAssemblyReload += BeforeAssemblyReload;
236 }
237
238 static void RegisterApplicationFocusHandlers()
239 {
240 EditorWindowFocus.OnApplicationActivated += OnApplicationActivated;
241
242 EditorWindowFocus.OnApplicationDeactivated += OnApplicationDeactivated;
243 }
244
245 static void RegisterExceptionHandlers()
246 {
247 AppDomain.CurrentDomain.UnhandledException += HandleUnhandledException;
248
249 Application.logMessageReceivedThreaded += HandleLog;
250 }
251
252 static void UnRegisterDomainUnloadHandler()
253 {
254 AppDomain.CurrentDomain.DomainUnload -= AppDomainUnload;
255 }
256
257 static void UnRegisterEditorWantsToQuit()
258 {
259 EditorApplication.wantsToQuit -= OnEditorWantsToQuit;
260 }
261
262 static void UnRegisterEditorQuitting()
263 {
264 EditorApplication.quitting -= OnEditorQuitting;
265 }
266
267 static void UnRegisterBeforeAssemblyReloadHandler()
268 {
269 AssemblyReloadEvents.beforeAssemblyReload -= BeforeAssemblyReload;
270 }
271
272 static void UnRegisterApplicationFocusHandlers()
273 {
274 EditorWindowFocus.OnApplicationActivated -= OnApplicationActivated;
275
276 EditorWindowFocus.OnApplicationDeactivated -= OnApplicationDeactivated;
277 }
278
279 static void UnRegisterExceptionHandlers()
280 {
281 AppDomain.CurrentDomain.UnhandledException -= HandleUnhandledException;
282
283 Application.logMessageReceivedThreaded -= HandleLog;
284 }
285
286 static void AppDomainUnload(object sender, EventArgs e)
287 {
288 mLog.Debug("AppDomainUnload");
289
290 UnRegisterDomainUnloadHandler();
291 }
292
293 static void HandleUnhandledException(object sender, UnhandledExceptionEventArgs args)
294 {
295 Exception ex = (Exception)args.ExceptionObject;
296
297 if (IsExitGUIException(ex) ||
298 !IsPlasticStackTrace(ex.StackTrace))
299 throw ex;
300
301 GUIActionRunner.RunGUIAction(delegate {
302 ExceptionsHandler.HandleException("HandleUnhandledException", ex);
303 });
304 }
305
306 static void HandleLog(string logString, string stackTrace, LogType type)
307 {
308 if (type != LogType.Exception)
309 return;
310
311 if (!IsPlasticStackTrace(stackTrace))
312 return;
313
314 GUIActionRunner.RunGUIAction(delegate {
315 mLog.ErrorFormat("[HandleLog] Unexpected error: {0}", logString);
316 mLog.DebugFormat("Stack trace: {0}", stackTrace);
317
318 string message = logString;
319 if (ExceptionsHandler.DumpStackTrace())
320 message += Environment.NewLine + stackTrace;
321
322 GuiMessage.ShowError(message);
323 });
324 }
325
326 static void OnApplicationActivated()
327 {
328 mLog.Debug("OnApplicationActivated");
329
330 EnableMonoFsWatcherIfNeeded();
331
332 // When the editor gets the focus back, we need to guarantee our status caches are cleared.
333 // This way we can reflect external changes that are not captured by the internal watchers.
334 if (PlasticPlugin.AssetStatusCache != null)
335 {
336 PlasticPlugin.AssetStatusCache.Clear();
337 }
338 }
339
340 static void OnApplicationDeactivated()
341 {
342 mLog.Debug("OnApplicationDeactivated");
343
344 DisableMonoFsWatcherIfNeeded();
345 }
346
347 static void OnEditorQuitting()
348 {
349 mLog.Debug("OnEditorQuitting");
350
351 PlasticPlugin.Shutdown();
352 }
353
354 static bool OnEditorWantsToQuit()
355 {
356 mLog.Debug("OnEditorWantsToQuit");
357
358 if (!HasRunningOperation())
359 return true;
360
361 return GuiMessage.ShowQuestion(
362 PlasticLocalization.GetString(PlasticLocalization.Name.OperationRunning),
363 PlasticLocalization.GetString(PlasticLocalization.Name.ConfirmClosingRunningOperation),
364 PlasticLocalization.GetString(PlasticLocalization.Name.YesButton));
365 }
366
367 static void BeforeAssemblyReload()
368 {
369 mLog.Debug("BeforeAssemblyReload");
370
371 UnRegisterBeforeAssemblyReloadHandler();
372
373 PlasticShutdown.Shutdown();
374 }
375
376 static void InitLocalization()
377 {
378 string language = null;
379 try
380 {
381 language = ClientConfig.Get().GetLanguage();
382 }
383 catch
384 {
385 language = string.Empty;
386 }
387
388 Localization.Init(language);
389 PlasticLocalization.SetLanguage(language);
390 }
391
392 static void SetupFsWatcher()
393 {
394 if (!PlatformIdentifier.IsMac())
395 return;
396
397 WorkspaceWatcherFsNodeReadersCache.Get().SetMacFsWatcherBuilder(
398 new MacFsWatcherBuilder());
399 }
400
401 static bool IsPlasticStackTrace(string stackTrace)
402 {
403 if (stackTrace == null)
404 return false;
405
406 string[] namespaces = new[] {
407 "Codice.",
408 "GluonGui.",
409 "PlasticGui."
410 };
411
412 return namespaces.Any(stackTrace.Contains);
413 }
414
415 static bool IsExitGUIException(Exception ex)
416 {
417 return ex is ExitGUIException;
418 }
419
420 static void ConfigureLogging()
421 {
422 try
423 {
424 string log4netpath = ToolConfig.GetUnityPlasticLogConfigFile();
425
426 if (!File.Exists(log4netpath))
427 WriteLogConfiguration.For(log4netpath);
428
429 XmlConfigurator.Configure(new FileInfo(log4netpath));
430
431 mLog.DebugFormat("Configured logging in '{0}'", log4netpath);
432 }
433 catch
434 {
435 //it failed configuring the logging info; nothing to do.
436 }
437 }
438
439 static bool mIsDomainUnloadHandlerRegistered;
440
441 static bool mIsInitialized;
442 static IWorkspaceWindow mWorkspaceWindow;
443 static WorkspaceInfo mWkInfo;
444 static EventSenderScheduler mEventSenderScheduler;
445 static PingEventLoop mPingEventLoop;
446 static readonly ILog mLog = PlasticApp.GetLogger("PlasticApp");
447 }
448}