| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Net;
- using System.Net.Http;
- using System.Net.Http.Headers;
- using System.Text;
- using System.Threading.Tasks;
- using JetBrains.Annotations;
- using Unity.Cloud.Collaborate.Assets;
- using Unity.Cloud.Collaborate.Models.Api;
- using Unity.Cloud.Collaborate.Models.Enums;
- using Unity.Cloud.Collaborate.Models.Structures;
- using Unity.Cloud.Collaborate.Utilities;
- using UnityEditor;
- using UnityEditor.Collaboration;
- using UnityEditor.Connect;
- using UnityEditorInternal;
- using UnityEngine;
- using UnityEngine.Assertions;
- using static UnityEditor.Collaboration.Collab;
- using ProgressInfo = UnityEditor.Collaboration.ProgressInfo;
- namespace Unity.Cloud.Collaborate.Models.Providers
- {
- internal class Collab : ISourceControlProvider
- {
- const string k_KServiceUrl = "developer.cloud.unity3d.com";
- readonly RevisionsService m_RevisionsService;
- /// <inheritdoc />
- public event Action UpdatedChangeList;
- /// <inheritdoc />
- public event Action<IReadOnlyList<string>> UpdatedSelectedChangeList;
- /// <inheritdoc />
- public event Action<bool> UpdatedConflictState;
- /// <inheritdoc />
- public event Action<bool> UpdatedRemoteRevisionsAvailability;
- /// <inheritdoc />
- public event Action<ProjectStatus> UpdatedProjectStatus;
- /// <inheritdoc />
- public event Action<bool> UpdatedOperationStatus;
- /// <inheritdoc />
- public event Action<IProgressInfo> UpdatedOperationProgress;
- /// <inheritdoc />
- public event Action<IErrorInfo> ErrorOccurred;
- /// <inheritdoc />
- public event Action ErrorCleared;
- readonly List<IChangeEntry> m_Changes;
- bool m_ConflictCachedState;
- bool m_RemoteRevisionsAvailableState;
- // History entry requesting bits and bobs.
- readonly Queue<(int offset, int size, Action<int?, IReadOnlyList<IHistoryEntry>>)> m_HistoryRequests;
- [NotNull]
- IReadOnlyList<IHistoryEntry> m_HistoryEntries;
- (int offset, int size)? m_HistoryEntriesCache;
- [CanBeNull]
- IHistoryEntry m_HistoryEntryCache;
- int? m_HistoryEntryCountCache;
- string m_TipCache;
- [CanBeNull]
- IErrorInfo m_ErrorInfo;
- [CanBeNull]
- IProgressInfo m_ProgressInfo;
- ProjectStatus m_ProjectStatus;
- public Collab()
- {
- m_RevisionsService = new RevisionsService(instance, UnityConnect.instance);
- m_Changes = new List<IChangeEntry>();
- m_HistoryEntries = new List<IHistoryEntry>();
- m_HistoryRequests = new Queue<(int offset, int size, Action<int?, IReadOnlyList<IHistoryEntry>>)>();
- // Get initial values.
- var info = instance.collabInfo;
- m_ConflictCachedState = info.conflict;
- m_RemoteRevisionsAvailableState = info.update;
- m_TipCache = info.tip;
- m_ProgressInfo = info.inProgress ? ProgressInfoFromCollab(instance.GetJobProgress(0)) : null;
- m_ErrorInfo = instance.GetError(UnityConnect.UnityErrorFilter.ByContext | UnityConnect.UnityErrorFilter.ByChild, out var errInfo)
- ? ErrorInfoFromUnity(errInfo)
- : null;
- m_ProjectStatus = GetNewProjectStatus(info, UnityConnect.instance.connectInfo, UnityConnect.instance.projectInfo);
- SetupEvents();
- }
- /// <summary>
- /// Setup events for the provider.
- /// </summary>
- void SetupEvents()
- {
- // just connect notifier events.
- instance.ChangeItemsChanged += OnChangeItemsChanged;
- instance.SelectedChangeItemsChanged += OnSelectedChangeItemsChanged;
- instance.RevisionUpdated_V2 += OnRevisionUpdated;
- instance.CollabInfoChanged += OnCollabInfoChanged;
- instance.JobsCompleted += OnJobsCompleted;
- instance.ErrorOccurred_V2 += OnErrorOccurred;
- instance.ErrorCleared += OnErrorCleared;
- instance.StateChanged += OnCollabStateChanged;
- UnityConnect.instance.StateChanged += OnUnityConnectStateChanged;
- UnityConnect.instance.ProjectStateChanged += OnUnityConnectProjectStateChanged;
- m_RevisionsService.FetchRevisionsCallback += OnReceiveHistoryEntries;
- }
- #region Callback & Helper Methods
- /// <summary>
- /// Event handler for when the change list has changed.
- /// </summary>
- /// <param name="changes">New change list.</param>
- /// <param name="isFiltered">Whether or not the list is filtered. Should always be false.</param>
- void OnChangeItemsChanged(ChangeItem[] changes, bool isFiltered)
- {
- UpdateChanges(changes);
- UpdatedChangeList?.Invoke();
- }
- /// <summary>
- /// WIP method to handle partial publish in collab.
- /// </summary>
- /// <param name="changes">Received changes.</param>
- /// <param name="isFiltered">Whether or not it's a partial publish. Should always be true.</param>
- void OnSelectedChangeItemsChanged(ChangeItem[] changes, bool isFiltered)
- {
- // This is used by selective commit. Assert all API calls to here are setting isFiltered to true !
- Debug.Assert(isFiltered);
- var selectedChanges = changes.Select(e => e.Path).ToList();
- UpdatedSelectedChangeList?.Invoke(selectedChanges);
- }
- /// <summary>
- /// Event handler for when a revision has been created or updated. It's not called 100% of the time when a user
- /// publishes a new revision.
- /// </summary>
- /// <param name="info">New collab info.</param>
- /// <param name="rev">New revision id.</param>
- /// <param name="action">Action that occured.</param>
- void OnRevisionUpdated(CollabInfo info, string rev, string action)
- {
- // Invalidate the cache.
- m_HistoryEntriesCache = null;
- m_HistoryEntryCache = null;
- m_HistoryEntryCountCache = null;
- // Send update event.
- UpdatedHistoryEntries?.Invoke();
- OnCollabInfoChanged(info);
- }
- void OnCollabInfoChanged(CollabInfo info)
- {
- // Update conflict state.
- if (m_ConflictCachedState != info.conflict)
- {
- m_ConflictCachedState = info.conflict;
- UpdatedConflictState?.Invoke(info.conflict);
- }
- // Update revisions available state.
- if (m_RemoteRevisionsAvailableState != info.update)
- {
- m_RemoteRevisionsAvailableState = info.update;
- UpdatedRemoteRevisionsAvailability?.Invoke(info.update);
- }
- // Update history list if the tip has changed.
- if (m_TipCache != info.tip)
- {
- m_TipCache = info.tip;
- // Invalidate the cache.
- m_HistoryEntriesCache = null;
- m_HistoryEntryCache = null;
- m_HistoryEntryCountCache = null;
- // Send update event.
- UpdatedHistoryEntries?.Invoke();
- }
- // Update project state
- UpdateProjectStatus(info, UnityConnect.instance.connectInfo, UnityConnect.instance.projectInfo);
- // Update progress state.
- if (info.inProgress)
- {
- // Get progress info.
- var progressInfo = instance.GetJobProgress(0);
- Assert.IsNotNull(progressInfo);
- // Trigger start operation if not already known.
- if (m_ProgressInfo == null)
- {
- UpdatedOperationStatus?.Invoke(true);
- }
- // Send progress info.
- m_ProgressInfo = ProgressInfoFromCollab(progressInfo);
- UpdatedOperationProgress?.Invoke(m_ProgressInfo);
- }
- else if (m_ProgressInfo != null)
- {
- // Signal end of job if job still exists
- m_ProgressInfo = null;
- UpdatedOperationStatus?.Invoke(false);
- }
- }
- void OnJobsCompleted(CollabInfo info)
- {
- // NOTE: The first start of collab sends a completion event with no prior progress info.
- // To handle this, skip sending completion event if there has been no start event.
- if (m_ProgressInfo == null) return;
- Assert.IsFalse(info.inProgress);
- m_ProgressInfo = null;
- UpdatedOperationStatus?.Invoke(false);
- }
- void OnErrorOccurred(UnityErrorInfo error)
- {
- if (m_ErrorInfo?.Code == error.code) return;
- m_ErrorInfo = ErrorInfoFromUnity(error);
- ErrorOccurred?.Invoke(m_ErrorInfo);
- }
- void OnErrorCleared()
- {
- m_ErrorInfo = null;
- ErrorCleared?.Invoke();
- }
- /// <summary>
- /// On receiving history result, remove the oldest request, send the received data, then make the next request.
- /// </summary>
- /// <param name="revisionsResult">Result from the history request.</param>
- void OnReceiveHistoryEntries(RevisionsResult revisionsResult)
- {
- Assert.AreNotEqual(0, m_HistoryRequests.Count, "There should be a history request.");
- var (offset, size, callback) = m_HistoryRequests.Dequeue();
- // Get results, cache, then send them.
- var results = revisionsResult?.Revisions.Select(RevisionToHistoryEntry).ToList();
- if (results != null)
- {
- m_HistoryEntries = results;
- m_HistoryEntriesCache = (offset, size);
- m_HistoryEntryCountCache = revisionsResult.RevisionsInRepo;
- callback(revisionsResult.RevisionsInRepo, m_HistoryEntries);
- }
- // Start the next request --> has to be outside of the callback.
- EditorApplication.delayCall += () => ConsumeHistoryQueue();
- }
- /// <summary>
- /// Event handler for receiving unity connect project state changes.
- /// </summary>
- /// <param name="projectInfo">New project info.</param>
- void OnUnityConnectProjectStateChanged(ProjectInfo projectInfo)
- {
- UpdateProjectStatus(instance.collabInfo, UnityConnect.instance.connectInfo, projectInfo);
- }
- /// <summary>
- /// Event handler for receiving collab state changes.
- /// </summary>
- /// <param name="info">New collab state.</param>
- void OnCollabStateChanged(CollabInfo info)
- {
- OnCollabInfoChanged(info);
- }
- /// <summary>
- /// Event handler for receiving collab state changes.
- /// </summary>
- /// <param name="connectInfo">UnityConnect connect info.</param>
- void OnUnityConnectStateChanged(ConnectInfo connectInfo)
- {
- UpdateProjectStatus(instance.collabInfo, connectInfo, UnityConnect.instance.projectInfo);
- }
- /// <summary>
- /// Update cached ready value and send event if it has changed.
- /// </summary>
- void UpdateProjectStatus(CollabInfo collabInfo, ConnectInfo connectInfo, ProjectInfo projectInfo)
- {
- var currentStatus = GetNewProjectStatus(collabInfo, connectInfo, projectInfo);
- if (m_ProjectStatus == currentStatus) return;
- m_ProjectStatus = currentStatus;
- UpdatedProjectStatus?.Invoke(m_ProjectStatus);
- }
- /// <summary>
- /// Returns the current project status.
- /// </summary>
- /// <returns>Current status of this project.</returns>
- static ProjectStatus GetNewProjectStatus(CollabInfo collabInfo, ConnectInfo connectInfo, ProjectInfo projectInfo)
- {
- // No UPID.
- if (!projectInfo.projectBound)
- {
- return ProjectStatus.Unbound;
- }
- if (!connectInfo.online)
- {
- return ProjectStatus.Offline;
- }
- if (connectInfo.maintenance || collabInfo.maintenance)
- {
- return ProjectStatus.Maintenance;
- }
- if (!connectInfo.loggedIn)
- {
- return ProjectStatus.LoggedOut;
- }
- if (!collabInfo.seat)
- {
- return ProjectStatus.NoSeat;
- }
- // UPID exists, but collab off.
- if (!instance.IsCollabEnabledForCurrentProject())
- {
- return ProjectStatus.Bound;
- }
- // Waiting for collab to connect and be ready.
- if (!instance.IsConnected() || !collabInfo.ready)
- {
- return ProjectStatus.Loading;
- }
- return ProjectStatus.Ready;
- }
- /// <summary>
- /// Consume the next entry on the history queue.
- /// </summary>
- /// <param name="afterEnqueue">True if an entry was just inserted. Starts the consumption cycle.</param>
- void ConsumeHistoryQueue(bool afterEnqueue = false)
- {
- // Start consuming the queue if the first entry was just enqueued.
- if (afterEnqueue && m_HistoryRequests.Count != 1) return;
- // Can't consume an empty queue.
- if (m_HistoryRequests.Count == 0) return;
- var (offset, size, callback) = m_HistoryRequests.Peek();
- // Execute next request. Discard if exception.
- try
- {
- m_RevisionsService.GetRevisions(offset, size);
- }
- catch (Exception e)
- {
- Debug.LogException(e);
- // Remove request and send failure callback.
- m_HistoryRequests.Dequeue();
- callback(null, null);
- }
- }
- /// <summary>
- /// Make a history request.
- /// </summary>
- /// <param name="offset">Offset for the request to start from.</param>
- /// <param name="size">Target length of the resultant list.</param>
- /// <param name="callback">Callback for the result.</param>
- void QueueHistoryRequest(int offset, int size, Action<int?, IReadOnlyList<IHistoryEntry>> callback)
- {
- m_HistoryRequests.Enqueue((offset, size, callback));
- ConsumeHistoryQueue(true);
- }
- /// <summary>
- /// Update cache of converted change entries from provided collab changes.
- /// </summary>
- /// <param name="changes">Received list of changes from collab.</param>
- void UpdateChanges(IEnumerable<Change> changes)
- {
- m_Changes.Clear();
- m_Changes.AddRange(changes.Select(change =>
- new ChangeEntry(change.path, change.path, ChangeEntryStatusFromCollabState(change.state),
- false, IsCollabStateFlagSet(change.state, CollabStates.kCollabConflicted | CollabStates.kCollabPendingMerge), change))
- .Cast<IChangeEntry>());
- }
- /// <summary>
- /// Update cache of converted change entries from provided collab changes.
- /// </summary>
- /// <param name="changes">Received list of changes from collab.</param>
- void UpdateChanges(IEnumerable<ChangeItem> changes)
- {
- m_Changes.Clear();
- m_Changes.AddRange(changes.Select(change =>
- new ChangeEntry(change.Path, change.Path, ChangeEntryStatusFromCollabState(change.State),
- false, IsCollabStateFlagSet(change.State, CollabStates.kCollabConflicted | CollabStates.kCollabPendingMerge), change))
- .Cast<IChangeEntry>());
- }
- /// <inheritdoc />
- public bool GetRemoteRevisionAvailability()
- {
- // Return cached value.
- return m_RemoteRevisionsAvailableState;
- }
- /// <inheritdoc />
- public bool GetConflictedState()
- {
- // Return cached value.
- return m_ConflictCachedState;
- }
- /// <inheritdoc />
- public IProgressInfo GetProgressState()
- {
- // Return cached value.
- return m_ProgressInfo;
- }
- /// <inheritdoc />
- public IErrorInfo GetErrorState()
- {
- return m_ErrorInfo;
- }
- /// <inheritdoc />
- public virtual ProjectStatus GetProjectStatus()
- {
- return m_ProjectStatus;
- }
- /// <inheritdoc />
- public void RequestChangeList(Action<IReadOnlyList<IChangeEntry>> callback)
- {
- var changes = instance.GetChangesToPublish_V2().changes;
- UpdateChanges(changes);
- callback(m_Changes);
- // Also check for errors.
- if (instance.GetError(UnityConnect.UnityErrorFilter.All, out var error) &&
- (CollabErrorCode)error.code != CollabErrorCode.Collab_ErrNone)
- {
- ErrorOccurred?.Invoke(ErrorInfoFromUnity(error));
- }
- }
- /// <inheritdoc />
- public void RequestPublish(string message, IReadOnlyList<IChangeEntry> changeEntries = null)
- {
- var changeItems = changeEntries?.Select(EntryToChangeItem).ToArray();
- instance.PublishAssetsAsync(message, changeItems);
- ChangeItem EntryToChangeItem(IChangeEntry entry)
- {
- return entry.Tag as ChangeItem;
- }
- }
- #endregion
- #region SourceControlHistoryCommands
- /// <inheritdoc />
- public event Action UpdatedHistoryEntries;
- /// <inheritdoc />
- public void RequestHistoryEntry(string revisionId, Action<IHistoryEntry> callback)
- {
- // Return cached entry if possible.
- if (m_HistoryEntryCache?.RevisionId == revisionId)
- {
- callback(m_HistoryEntryCache);
- return;
- }
- // Ensure that a cleanup occurs in the case of an exception.
- m_RevisionsService.FetchSingleRevisionCallback += OnFetchRevisionCallback;
- try
- {
- m_RevisionsService.GetRevision(revisionId);
- }
- catch (Exception e)
- {
- Debug.LogException(e);
- m_RevisionsService.FetchSingleRevisionCallback -= OnFetchRevisionCallback;
- callback(null);
- }
- void OnFetchRevisionCallback(Revision? revision)
- {
- m_RevisionsService.FetchSingleRevisionCallback -= OnFetchRevisionCallback;
- // Failing to find the revision can result in a null revision or an empty revisionID.
- callback(string.IsNullOrEmpty(revision?.revisionID)
- ? null
- : RevisionToHistoryEntry(revision.GetValueOrDefault()));
- }
- }
- /// <inheritdoc />
- public void RequestHistoryPage(int offset, int pageSize, Action<IReadOnlyList<IHistoryEntry>> callback)
- {
- // Return cached entry is possible.
- if (m_HistoryEntriesCache?.offset == offset && m_HistoryEntriesCache?.size == pageSize)
- {
- callback(m_HistoryEntries);
- return;
- }
- // Queue up the request.
- QueueHistoryRequest(offset, pageSize, (_, r) => callback(r));
- }
- /// <inheritdoc />
- public void RequestHistoryCount(Action<int?> callback)
- {
- // Return cached value if possible.
- if (m_HistoryEntryCountCache != null)
- {
- callback(m_HistoryEntryCountCache);
- return;
- }
- QueueHistoryRequest(0, 0, (c, _) => callback(c));
- }
- /// <inheritdoc />
- public void RequestDiscard(IChangeEntry entry)
- {
- // Collab cannot revert a new file as it has nothing to go back to. So, instead we delete them.
- if (entry.Status == ChangeEntryStatus.Added)
- {
- File.Delete(entry.Path);
- // Notify ADB to refresh since a change has been made.
- AssetDatabase.Refresh();
- }
- else
- {
- instance.RevertFile(entry.Path, true);
- }
- }
- /// <inheritdoc />
- public void RequestBulkDiscard(IReadOnlyList<IChangeEntry> entries)
- {
- var revertEntries = new List<ChangeItem>();
- var deleteOccured = false;
- foreach (var entry in entries)
- {
- // Collab cannot revert a new file as it has nothing to go back to. So, instead we delete them.
- if (entry.Status == ChangeEntryStatus.Added)
- {
- File.Delete(entry.Path);
- deleteOccured = true;
- }
- else
- {
- revertEntries.Add((ChangeItem)entry.Tag);
- }
- }
- // If a change has been made, notify the ADB to refresh.
- if (deleteOccured)
- {
- AssetDatabase.Refresh();
- }
- instance.RevertFiles(revertEntries.ToArray(), true);
- }
- /// <inheritdoc />
- public void RequestDiffChanges(string path)
- {
- instance.ShowDifferences(path);
- }
- /// <inheritdoc />
- public bool SupportsRevert { get; } = false;
- /// <inheritdoc />
- public void RequestRevert(string revisionId, IReadOnlyList<string> files)
- {
- throw new NotImplementedException();
- }
- /// <inheritdoc />
- public void RequestUpdateTo(string revisionId)
- {
- instance.Update(revisionId, true);
- }
- /// <inheritdoc />
- public void RequestRestoreTo(string revisionId)
- {
- instance.ResyncToRevision(revisionId);
- }
- /// <inheritdoc />
- public void RequestGoBackTo(string revisionId)
- {
- instance.GoBackToRevision(revisionId, false);
- }
- /// <inheritdoc />
- public void ClearError()
- {
- instance.ClearErrors();
- }
- /// <inheritdoc />
- public void RequestShowConflictedDifferences(string path)
- {
- if (UnityEditor.Collaboration.Collab.IsDiffToolsAvailable())
- {
- instance.ShowConflictDifferences(path);
- }
- else
- {
- Debug.Log(StringAssets.noMergeToolIsConfigured);
- }
- }
- /// <inheritdoc />
- public void RequestChooseMerge(string path)
- {
- if (UnityEditor.Collaboration.Collab.IsDiffToolsAvailable())
- {
- instance.LaunchConflictExternalMerge(path);
- }
- else
- {
- Debug.Log(StringAssets.noMergeToolIsConfigured);
- }
- }
- /// <inheritdoc />
- public void RequestChooseMine(string[] paths)
- {
- instance.SetConflictsResolvedMine(paths);
- }
- /// <inheritdoc />
- public void RequestChooseRemote(string[] paths)
- {
- instance.SetConflictsResolvedTheirs(paths);
- }
- /// <inheritdoc />
- public void RequestSync()
- {
- QueueHistoryRequest(0, 1, Callback);
- void Callback(int? count, IReadOnlyList<IHistoryEntry> revisions)
- {
- if (revisions != null && revisions.Count > 0)
- {
- instance.Update(revisions[0].RevisionId, true);
- }
- else
- {
- Debug.LogError("Remote revision id is unknown. Please try again.");
- }
- }
- }
- /// <inheritdoc />
- public void RequestCancelJob()
- {
- instance.CancelJob(0);
- }
- /// <inheritdoc />
- public virtual void ShowServicePage()
- {
- SettingsService.OpenProjectSettings("Project/Services/Collaborate");
- }
- /// <inheritdoc />
- public void ShowLoginPage()
- {
- UnityConnect.instance.ShowLogin();
- }
- /// <inheritdoc />
- public void ShowNoSeatPage()
- {
- var unityConnect = UnityConnect.instance;
- var env = unityConnect.GetEnvironment();
- // Map environment to url - prod is special
- if (env == "production")
- env = "";
- else
- env += "-";
- var url = "https://" + env + k_KServiceUrl
- + "/orgs/" + unityConnect.GetOrganizationId()
- + "/projects/" + unityConnect.GetProjectName()
- + "/unity-teams/";
- Application.OpenURL(url);
- }
- /// <inheritdoc />
- public async void RequestTurnOnService()
- {
- try
- {
- await RequestTurnOnServiceInternal();
- }
- catch (Exception e)
- {
- Debug.LogException(e);
- }
- }
- protected async Task RequestTurnOnServiceInternal()
- {
- Assert.IsTrue(Threading.IsMainThread, "This must be run on the main thread.");
- // Fire up the update Genesis service flag request.
- var http = new HttpClientHandler { CookieContainer = new CookieContainer() };
- var client = new HttpClient(http);
- var projectGuid = UnityConnect.instance.projectInfo.projectGUID;
- var accessToken = UnityConnect.instance.GetAccessToken();
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
- client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
- client.DefaultRequestHeaders.TryAddWithoutValidation("X-UNITY-VERSION", InternalEditorUtility.GetFullUnityVersion());
- var fullUrl = $"{UnityConnect.instance.GetConfigurationURL(CloudConfigUrl.CloudCore)}/api/projects/{projectGuid}/service_flags";
- const string json = @"{ ""service_flags"": { ""collab"" : true} }";
- var content = new StringContent(json, Encoding.UTF8, "application/json");
- var response = await PutAsync(client, fullUrl, content);
- // Success.
- if (response?.StatusCode == HttpStatusCode.OK)
- {
- SaveAssets();
- TurnOnCollabInternal();
- }
- // Error.
- else if (response?.StatusCode == HttpStatusCode.Forbidden)
- {
- ShowCredentialsError();
- }
- else
- {
- ShowGeneralError();
- }
- }
- protected virtual void SaveAssets()
- {
- instance.SaveAssets();
- }
- protected virtual Task<HttpResponseMessage> PutAsync(HttpClient client, string fullUrl, StringContent content)
- {
- return client.PutAsync(fullUrl, content);
- }
- protected virtual void TurnOnCollabInternal()
- {
- // enable the server from the client..
- instance.SetCollabEnabledForCurrentProject(true);
- // persist by marking collab on in settings
- PlayerSettings.SetCloudServiceEnabled("Collab", true);
- }
- protected virtual void ShowCredentialsError()
- {
- // TODO - ahmad :- Show an Error UI.
- Debug.LogError("You need owner privilege to enable or disable collab.");
- }
- protected virtual void ShowGeneralError()
- {
- // TODO - ahmad :- Show an Error UI.
- Debug.LogError("cannot enable collab");
- }
- #endregion
- #region Static Helper Methods
- /// <summary>
- /// Converts a Collab Revision to an IHistoryEntry.
- /// </summary>
- /// <param name="revision">Revision to convert.</param>
- /// <returns>Resultant IHistoryEntry</returns>
- IHistoryEntry RevisionToHistoryEntry(Revision revision)
- {
- var time = DateTimeOffset.FromUnixTimeSeconds((long)revision.timeStamp);
- var entries = revision.entries.Select(ChangeActionToChangeEntry).ToList();
- var status = HistoryEntryStatus.Ahead;
- if (revision.isObtained)
- status = HistoryEntryStatus.Behind;
- if (revision.revisionID == m_RevisionsService.tipRevision)
- status = HistoryEntryStatus.Current;
- return new HistoryEntry(revision.revisionID, status, revision.author, revision.comment, time, entries);
- }
- /// <summary>
- /// Converts a Collab ChangeAction to an IChangeEntry.
- /// </summary>
- /// <param name="action">ChangeAction to convert.</param>
- /// <returns>Resultant IChangeEntry</returns>
- static IChangeEntry ChangeActionToChangeEntry(ChangeAction action)
- {
- var unmerged = false;
- var status = ChangeEntryStatus.None;
- switch (action.action.ToLower())
- {
- case "added":
- status = ChangeEntryStatus.Added;
- break;
- case "conflict":
- status = ChangeEntryStatus.Unmerged;
- unmerged = true;
- break;
- case "deleted":
- status = ChangeEntryStatus.Deleted;
- break;
- case "ignored":
- status = ChangeEntryStatus.Ignored;
- break;
- case "renamed":
- case "moved":
- status = ChangeEntryStatus.Renamed;
- break;
- case "updated":
- status = ChangeEntryStatus.Modified;
- break;
- default:
- Debug.LogError($"Unknown file status: {action.action}");
- break;
- }
- return new ChangeEntry(action.path, status: status, unmerged: unmerged);
- }
- /// <summary>
- /// Converts a Collab CollabStates to an ChangeEntryStatus.
- /// Note that CollabStates is a bitwise flag, while
- /// ChangeEntryStatus is an enum, so ordering matters.
- /// </summary>
- /// <param name="state">ChangeAction to convert.</param>
- /// <returns>Resultant ChangeEntryStatus</returns>
- static ChangeEntryStatus ChangeEntryStatusFromCollabState(CollabStates state)
- {
- if (IsCollabStateFlagSet(state, CollabStates.kCollabIgnored))
- {
- return ChangeEntryStatus.Ignored;
- }
- if (IsCollabStateFlagSet(state, CollabStates.kCollabConflicted | CollabStates.kCollabPendingMerge))
- {
- return ChangeEntryStatus.Unmerged;
- }
- if (IsCollabStateFlagSet(state, CollabStates.kCollabAddedLocal))
- {
- return ChangeEntryStatus.Added;
- }
- if (IsCollabStateFlagSet(state, CollabStates.kCollabMovedLocal))
- {
- return ChangeEntryStatus.Renamed;
- }
- if (IsCollabStateFlagSet(state, CollabStates.kCollabDeletedLocal))
- {
- return ChangeEntryStatus.Deleted;
- }
- if (IsCollabStateFlagSet(state, CollabStates.kCollabCheckedOutLocal))
- {
- return ChangeEntryStatus.Modified;
- }
- return ChangeEntryStatus.Unknown;
- }
- /// <summary>
- /// Checks the state of a flag in CollabStates.
- /// </summary>
- /// <param name="state">State to check from.</param>
- /// <param name="flag">Flag to check in the state.</param>
- /// <returns>True if flag is set.</returns>
- static bool IsCollabStateFlagSet(CollabStates state, CollabStates flag)
- {
- return (state & flag) != 0;
- }
- static IProgressInfo ProgressInfoFromCollab([CanBeNull] ProgressInfo collabProgress)
- {
- if (collabProgress == null) return null;
- return new Structures.ProgressInfo(
- collabProgress.title,
- collabProgress.extraInfo,
- collabProgress.currentCount,
- collabProgress.totalCount,
- collabProgress.lastErrorString,
- collabProgress.lastError,
- collabProgress.canCancel,
- collabProgress.isProgressTypePercent,
- collabProgress.percentComplete);
- }
- static IErrorInfo ErrorInfoFromUnity(UnityErrorInfo error)
- {
- return new ErrorInfo(
- error.code,
- error.priority,
- error.behaviour,
- error.msg,
- error.shortMsg,
- error.codeStr);
- }
- #endregion
- enum CollabErrorCode
- {
- Collab_ErrNone = 0,
- Collab_Error,
- Collab_ErrProjectNotLinked,
- Collab_ErrNoSuchRepository,
- Collab_ErrNotLoggedIn,
- Collab_ErrNotConnected,
- Collab_ErrLocalCache,
- Collab_ErrNotUpToDate,
- Collab_ErrCannotGetRevision,
- Collab_ErrCannotGetRemote,
- Collab_ErrCannotGetLocal,
- Collab_ErrInvalidHost,
- Collab_ErrInvalidPort,
- Collab_ErrInvalidRevision,
- Collab_ErrNotSnapshot,
- Collab_ErrNoSuchRemoteFile,
- Collab_ErrNoSuchLocalFile,
- Collab_ErrJobNotDefined,
- Collab_ErrJobAlreadyRunning,
- Collab_ErrAlreadyUpToDate,
- Collab_ErrJobNotRunning,
- Collab_ErrNotSupported,
- Collab_ErrJobCancelled,
- Collab_ErrCannotSubmitChanges,
- Collab_ErrMD5DoesNotMatch,
- Collab_ErrRemoteChanged,
- Collab_ErrCannotCreateTempDir,
- Collab_ErrCannotDownloadEntry,
- Collab_ErrCannotCreatePath,
- Collab_ErrCannotCreateFile,
- Collab_ErrCannotCopyFile,
- Collab_ErrCannotMoveFile,
- Collab_ErrCannotDeleteFile,
- Collab_ErrCannotGetProjects,
- Collab_ErrCannotRestoreSnapshot,
- Collab_ErrFileWasAddedLocally,
- Collab_ErrFileIsModified,
- Collab_ErrFileIsMissing,
- Collab_ErrFileAlreadyExists,
- Collab_ErrAutomaticMergeBaseIsMissing,
- Collab_ErrSmartMergeConflicts,
- Collab_ErrTextMergeConflicts,
- Collab_ErrAutomaticMerge,
- Collab_ErrSmartMerge,
- Collab_ErrTextMerge,
- Collab_ErrExternalDiff,
- Collab_ErrExternalMerge,
- Collab_ErrParseJson,
- Collab_ErrWrongSerializationMode,
- Collab_ErrNoDiffRevisions,
- Collab_ErrWorkspaceChanged,
- Collab_ErrRefreshChannelAccess,
- Collab_ErrUpdateInProgress,
- Collab_ErrSoftLocksJobRunning,
- Collab_ErrCannotGetSoftLocks,
- Collab_ErrPostSoftLocks,
- Collab_ErrRequestCancelled,
- Collab_ErrCollabInErrorState,
- Collab_ErrUsageExceeded,
- Collab_ErrRepositoryLocked,
- Collab_ErrJobWaitingForSubTasks,
- Collab_ErrBadRequest = 400,
- Collab_ErrNotAuthorized = 401,
- Collab_ErrInternalServerError = 500,
- Collab_ErrBadGateway = 502,
- Collab_ErrServerUnavailable = 503,
- Collab_ErrSmartMergeSetConflictState,
- Collab_ErrTextMergeSetConflictState,
- Collab_ErrExternalMergeSetConflictState,
- Collab_ErrNoDiffMergeToolsConfigured,
- Collab_ErrUnsupportedDiffMergeToolConfigured,
- Collab_ErrNoSeat,
- Collab_ErrNoSeatHidden
- }
- }
- }
|