123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using JetBrains.Annotations;
- using Unity.Cloud.Collaborate.Models.Api;
- using Unity.Cloud.Collaborate.Models.Structures;
- using Unity.Cloud.Collaborate.UserInterface;
- using Unity.Cloud.Collaborate.Utilities;
- using UnityEngine;
- using UnityEngine.Assertions;
- namespace Unity.Cloud.Collaborate.Models
- {
- internal class ChangesModel : IChangesModel
- {
- protected readonly ISourceControlProvider m_Provider;
- /// <inheritdoc />
- public event Action UpdatedChangeList;
- /// <inheritdoc />
- public event Action OnUpdatedSelectedChanges;
- /// <inheritdoc />
- public event Action<bool> BusyStatusUpdated;
- /// <inheritdoc />
- public event Action StateChanged;
- internal Dictionary<string, IChangeEntryData> entryData;
- internal Dictionary<string, bool> toggledEntries;
- IReadOnlyList<IChangeEntryData> m_Conflicted;
- readonly ChangeEntryData m_AllItem;
- readonly HashSet<string> m_Requests;
- const string k_RequestNewList = "request-new-list";
- /// <inheritdoc />
- public string SavedRevisionSummary { get; set; }
- /// <inheritdoc />
- public string SavedSearchQuery { get; set; }
- /// <inheritdoc />
- public int ToggledCount { get; private set; }
- /// <inheritdoc />
- public int TotalCount { get; private set; }
- /// <inheritdoc />
- public int ConflictedCount => m_Conflicted.Count;
- /// <inheritdoc />
- public bool Conflicted => m_Provider.GetConflictedState();
- /// <inheritdoc />
- public bool Busy => m_Requests.Count != 0;
- public ChangesModel(ISourceControlProvider provider)
- {
- m_Provider = provider;
- m_Requests = new HashSet<string>();
- m_AllItem = new ChangeEntryData { Entry = new ChangeEntry(string.Empty), All = true };
- entryData = new Dictionary<string, IChangeEntryData>();
- m_Conflicted = new List<IChangeEntryData>();
- toggledEntries = new Dictionary<string, bool>();
- SavedSearchQuery = string.Empty;
- SavedRevisionSummary = string.Empty;
- }
- /// <inheritdoc />
- public void OnStart()
- {
- // Setup events.
- m_Provider.UpdatedChangeList += OnUpdatedChangeList;
- m_Provider.UpdatedSelectedChangeList += OnUpdatedSelectedChangesList;
- }
- /// <inheritdoc />
- public void OnStop()
- {
- // Clean up.
- m_Provider.UpdatedChangeList -= OnUpdatedChangeList;
- m_Provider.UpdatedSelectedChangeList -= OnUpdatedSelectedChangesList;
- }
- /// <inheritdoc />
- public void RestoreState(IWindowCache cache)
- {
- // Populate data from cache.
- SavedRevisionSummary = cache.RevisionSummary;
- SavedSearchQuery = cache.ChangesSearchValue;
- toggledEntries = cache.SimpleSelectedItems ?? new Dictionary<string, bool>();
- StateChanged?.Invoke();
- }
- /// <inheritdoc />
- public void SaveState(IWindowCache cache)
- {
- // Save data.
- cache.RevisionSummary = SavedRevisionSummary;
- cache.ChangesSearchValue = SavedSearchQuery;
- cache.SimpleSelectedItems = new SelectedItemsDictionary(toggledEntries);
- }
- /// <summary>
- /// Event handler for when the source control provider receives an updated history list.
- /// </summary>
- void OnUpdatedChangeList()
- {
- // Only one request at a time.
- if (!AddRequest(k_RequestNewList)) return;
- m_Provider.RequestChangeList(OnReceivedChangeList);
- }
- void OnUpdatedSelectedChangesList(IReadOnlyList<string> list)
- {
- ToggleAllEntries(false);
- foreach (var path in list)
- {
- UpdateEntryToggle(path, true);
- }
- OnUpdatedSelectedChanges?.Invoke();
- }
- /// <summary>
- /// Event handler to receive changes from the provider.
- /// </summary>
- /// <param name="list">Change list received.</param>
- void OnReceivedChangeList([CanBeNull] IReadOnlyList<IChangeEntry> list)
- {
- if (list != null)
- {
- UpdateChangeList(list);
- UpdatedChangeList?.Invoke();
- }
- else
- {
- Debug.LogError("Failed to fetch latest change list.");
- }
- RemoveRequest(k_RequestNewList);
- }
- /// <summary>
- /// Convert and cache new list of changes.
- /// </summary>
- /// <param name="list">New list of changes.</param>
- internal virtual void UpdateChangeList([NotNull] IReadOnlyList<IChangeEntry> list)
- {
- TotalCount = list.Count;
- // Create a new set of containers.
- var newEntryData = new Dictionary<string, IChangeEntryData> { [string.Empty] = m_AllItem };
- var newToggledEntries = new Dictionary<string, bool>();
- var conflicted = new List<IChangeEntryData>();
- var all = m_AllItem.Toggled;
- var toggledCount = 0;
- foreach (var entry in list)
- {
- // Transfer toggled state from old lookup into new.
- toggledEntries.TryGetValue(entry.Path, out var toggled);
- toggled = toggled || all || entry.Staged;
- newToggledEntries[entry.Path] = toggled;
- // Create a new data item for the entry.
- var item = new ChangeEntryData { Entry = entry, Toggled = toggled };
- newEntryData.Add(entry.Path, item);
- // Update counts.
- if (toggled)
- {
- toggledCount++;
- }
- if (entry.Unmerged)
- {
- conflicted.Add(item);
- }
- }
- // Store the new containers.
- entryData = newEntryData;
- toggledEntries = newToggledEntries;
- ToggledCount = toggledCount;
- m_Conflicted = conflicted;
- UpdateAllItemToggle();
- }
- /// <inheritdoc />
- public virtual bool UpdateEntryToggle(string path, bool toggled)
- {
- var entry = (ChangeEntryData)entryData[path];
- // Toggle all items if needed.
- if (entry.All)
- {
- return ToggleAllEntries(toggled);
- }
- // Update the toggled count.
- if (entry.Toggled && !toggled)
- {
- ToggledCount--;
- }
- else if (!entry.Toggled && toggled)
- {
- ToggledCount++;
- }
- // Store the value in the dictionary and data item.
- toggledEntries[entry.Entry.Path] = toggled;
- entry.Toggled = toggled;
- // Update the "All" option if needed.
- return UpdateAllItemToggle();
- }
- /// <inheritdoc />
- public IReadOnlyList<IChangeEntryData> GetToggledEntries(string query = null)
- {
- // Filter items by search query
- query = StringUtility.TrimAndToLower(query);
- return entryData.Values.Where(e => !e.All && e.Toggled && e.Entry.Path.ToLower().Contains(query)).ToList();
- }
- /// <inheritdoc />
- public IReadOnlyList<IChangeEntryData> GetUntoggledEntries(string query = null)
- {
- // Filter items by search query
- query = StringUtility.TrimAndToLower(query);
- return entryData.Values.Where(e => !e.All && !e.Toggled && e.Entry.Path.ToLower().Contains(query)).ToList();
- }
- /// <inheritdoc />
- public IReadOnlyList<IChangeEntryData> GetAllEntries(string query = null)
- {
- // Filter items by search query
- query = StringUtility.TrimAndToLower(query);
- return entryData.Values.Where(e => e.Entry.Path.ToLower().Contains(query)).ToList();
- }
- /// <inheritdoc />
- public IReadOnlyList<IChangeEntryData> GetConflictedEntries(string query = null)
- {
- // Filter items by search query
- query = StringUtility.TrimAndToLower(query);
- return entryData.Values.Where(e => !e.All && e.Conflicted && e.Entry.Path.ToLower().Contains(query))
- .ToList();
- }
- /// <summary>
- /// Update the state of the "All" entry. If all entries are toggled, then "All" should be toggled too;
- /// otherwise, "All" should be untoggled.
- /// </summary>
- /// <returns>True if the "All" entry was modified.</returns>
- bool UpdateAllItemToggle()
- {
- // Update state of the "All" option
- var allItemToggled = m_AllItem.Toggled;
- if (entryData.Count == 0) return false;
- if (ToggledCount == entryData.Count - 1)
- {
- // If every entry is toggled, then set AllItem as toggled.
- toggledEntries[m_AllItem.Entry.Path] = true;
- m_AllItem.Toggled = true;
- return !allItemToggled;
- }
- // Otherwise, set AllItem as not toggled.
- toggledEntries[m_AllItem.Entry.Path] = false;
- m_AllItem.Toggled = false;
- return allItemToggled;
- }
- /// <summary>
- /// Toggle on or off all entries in the list.
- /// </summary>
- /// <param name="toggled">Whether to toggle off or on.</param>
- /// <returns>True if the list has been modified.</returns>
- bool ToggleAllEntries(bool toggled)
- {
- // Update all values in the dictionary.
- toggledEntries.Keys.ToList().ForEach(x => toggledEntries[x] = toggled);
- // Compute the number of toggled items (excluding the single All).
- if (toggled)
- {
- ToggledCount = entryData.Count - 1;
- }
- else
- {
- ToggledCount = 0;
- }
- // Update all values in the list.
- foreach (var kv in entryData)
- {
- ((ChangeEntryData)kv.Value).Toggled = toggled;
- }
- return true;
- }
- /// <summary>
- /// Add a started request.
- /// </summary>
- /// <param name="requestId">Id of the request to add.</param>
- /// <returns>False if the request already exists.</returns>
- bool AddRequest(string requestId)
- {
- if (m_Requests.Contains(requestId)) return false;
- m_Requests.Add(requestId);
- // Signal background activity if this is the only thing running.
- if (m_Requests.Count == 1)
- BusyStatusUpdated?.Invoke(true);
- return true;
- }
- /// <summary>
- /// Remove a finished request.
- /// </summary>
- /// <param name="requestId">Id of the request to remove.</param>
- void RemoveRequest(string requestId)
- {
- Assert.IsTrue(m_Requests.Contains(requestId), $"Expects request to have first been made for it to have been finished: {requestId}");
- m_Requests.Remove(requestId);
- // Signal no background activity if no requests in progress
- if (m_Requests.Count == 0)
- BusyStatusUpdated?.Invoke(false);
- }
- /// <inheritdoc />
- public void RequestInitialData()
- {
- // Only one request at a time.
- if (!AddRequest(k_RequestNewList)) return;
- m_Provider.RequestChangeList(OnReceivedChangeList);
- }
- /// <inheritdoc />
- public void RequestDiffChanges(string path)
- {
- m_Provider.RequestDiffChanges(path);
- }
- /// <inheritdoc />
- public void RequestDiscard(IChangeEntry entry)
- {
- m_Provider.RequestDiscard(entry);
- }
- /// <inheritdoc />
- public void RequestBulkDiscard(IReadOnlyList<IChangeEntry> entries)
- {
- m_Provider.RequestBulkDiscard(entries);
- }
- /// <inheritdoc />
- public void RequestPublish(string message, IReadOnlyList<IChangeEntry> changes)
- {
- m_Provider.RequestPublish(message, changes);
- }
- /// <inheritdoc />
- public void RequestShowConflictedDifferences(string path)
- {
- m_Provider.RequestShowConflictedDifferences(path);
- }
- /// <inheritdoc />
- public void RequestChooseMerge(string path)
- {
- m_Provider.RequestChooseMerge(path);
- }
- /// <inheritdoc />
- public void RequestChooseMine(string[] paths)
- {
- m_Provider.RequestChooseMine(paths);
- }
- /// <inheritdoc />
- public void RequestChooseRemote(string[] paths)
- {
- m_Provider.RequestChooseRemote(paths);
- }
- /// <summary>
- /// Implementation of IChangeEntryData with each field given a setter so that the data can be updated.
- /// </summary>
- class ChangeEntryData : IChangeEntryData
- {
- /// <inheritdoc />
- public IChangeEntry Entry { get; set; }
- /// <inheritdoc />
- public bool Toggled { get; set; }
- /// <inheritdoc />
- public bool All { get; set; }
- /// <inheritdoc />
- public bool ToggleReadOnly => Entry.Staged;
- /// <inheritdoc />
- public bool Conflicted => Entry.Unmerged;
- }
- }
- }
|