/// ------------------------------------------------------------------------------------ /// <summary> /// Clears all marks and bundles. /// </summary> /// ------------------------------------------------------------------------------------ private void Clear() { m_currentBundle = null; ClearAllMarks(); m_undoBundles.Clear(); m_redoBundles.Clear(); }
/// <summary> /// A change is affected by redoing another unit of work if it affects any of the same objects, /// or if it removes a reference to an item which the other change deletes (and so redoing the /// other change first would delete the target without first removing the reference). /// </summary> internal bool IsAffectedByRedoing(FdoUnitOfWork other) { if (AffectsSameObjects(other)) { return(true); } //// We have a problem if the other change creates a reference to an object, and this change //// creates the object. If we redo the other change first, the reference will exist without a target. //if (m_newObjects.Count != 0) //{ // var guids = new HashSet<Guid>(from obj in m_newObjects select obj.Id.Guid); // foreach (var item in other.m_changes) // { // var change = item as FdoStateChangeBase; // if (change != null && change.RefersToAfterChange(guids)) // return true; // } //} //// We have a problem if this change deletes a reference to an object, and the other change //// deletes the object. If we redo the other change first, the object will be deleted, but not //// the reference to it. //if (other.m_deletedObjects.Count == 0) // return false; //var deletedGuids = new HashSet<Guid>(from obj in other.m_deletedObjects select obj.Guid); //foreach (var item in m_changes) //{ // var change = item as FdoStateChangeBase; // if (change != null && change.RefersToBeforeChange(deletedGuids)) // return true; //} return(false); }
public UndoStack(UnitOfWorkService uowService) { m_uowService = uowService; m_undoBundles = new Stack <FdoUnitOfWork>(); m_redoBundles = new Stack <FdoUnitOfWork>(); m_currentBundle = null; }
/// <summary> /// Rollback the current UOW. ///</summary> /// <param name='nDepth'>[Not used.]</param> /// <exception cref="InvalidOperationException"> /// Thrown if not in the right state to do /// a rollback (in the data change phase of the UOW). /// </exception> public void Rollback(int nDepth) { if (this != m_uowService.ActiveUndoStack) { m_uowService.ActiveUndoStack.Rollback(nDepth); return; } if (m_uowService.CurrentProcessingState != UnitOfWorkService.FdoBusinessTransactionState.ProcessingDataChanges) { throw new InvalidOperationException("Rollback not supported in the current state."); } Debug.Assert(m_uowService.m_lock.IsWriteLockHeld, "Trying Rollback without write lock!"); if (m_uowService.m_lock.IsWriteLockHeld) { m_uowService.m_lock.ExitWriteLock(); } else { Logger.WriteEvent("Trying to rollback without write lock!"); } m_currentBundle.Rollback(); m_currentBundle = null; m_actionsToDoAtEndOfPropChanged.Clear(); // don't do them on some subsequent task m_uowService.CurrentProcessingState = UnitOfWorkService.FdoBusinessTransactionState.ReadyForBeginTask; }
/// ------------------------------------------------------------------------------------ /// <summary> /// Raises the prop changed completed event. /// </summary> /// <param name="uow">The unit of work we are doing, undoing, or redoing.</param> /// <param name="fromUndoRedo">True if the method was called for an undo or /// redo, false for the original action.</param> /// ------------------------------------------------------------------------------------ private void DoTasksForEndOfPropChanged(FdoUnitOfWork uow, bool fromUndoRedo) { var tasks = new List <Action>(); if (!fromUndoRedo) { // These notifications must be sent only once. // In particular they must not be sent again if one of the tasks in the list makes a new UOW. // Since this method will execute at the end of such a task, we must make sure that there is // a fresh m_actionsToDoAtEndOfPropChanged for any such UOW so it will not see our list. tasks.AddRange(m_actionsToDoAtEndOfPropChanged); m_actionsToDoAtEndOfPropChanged.Clear(); } tasks.AddRange(uow.ActionsToDoAtEndOfPropChanged); if (tasks.Count > 0) { m_ui.SynchronizeInvoke.Invoke(() => { foreach (Action task in tasks) { task(); } }); } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Inserts the actions from another unit of work (merge) into this UOW at the beginning /// of this change list. This method handles the deleted/created objects correctly. /// </summary> /// <param name="uow">The unit of work.</param> /// ------------------------------------------------------------------------------------ internal void InsertActionsFrom(FdoUnitOfWork uow) { m_changes.InsertRange(0, uow.m_changes); foreach (Guid guid in uow.m_newGuids) { m_newGuids.Add(guid); } m_newObjects.UnionWith(uow.m_newObjects); m_dirtyObjects.UnionWith(uow.m_dirtyObjects); m_deletedObjects.UnionWith(uow.m_deletedObjects); }
/// <summary> /// Begin a sequence of non-undoable actions. ///</summary> public void BeginNonUndoableTask() { if (this != m_uowService.ActiveUndoStack) { m_uowService.ActiveUndoStack.BeginNonUndoableTask(); return; } CheckNotProcessingDataChanges("Nested tasks are not supported."); CheckNotBroadcastingPropChanges("Can't start new task, while broadcasting PropChanges."); CheckNotInUndoRedo(); m_uowService.CurrentProcessingState = UnitOfWorkService.FdoBusinessTransactionState.ProcessingDataChanges; m_uowService.m_lock.EnterWriteLock(); m_currentBundle = new FdoNonUndoableUnitOfWork(m_uowService); }
/// <summary> /// This undo stack has a change that conflicts with itemToUndo if it has a redoable item with an earlier /// sequence number that is affected by redoing itemToRedo. /// </summary> internal bool HasConflictingRedoChanges(FdoUnitOfWork itemToRedo) { // Stack enumeration is in reverse order, so we get the first change we might redo first. foreach (var other in m_redoBundles) { // If it's equal we're comparing with ourself, which should not be considered a conflict! if (other.Sequence >= itemToRedo.Sequence) { return(false); // this and therefore all remaining changes are later than the one we are testing } if (other.IsAffectedByRedoing(itemToRedo)) { return(true); } } return(false); }
/// <summary> /// Rollback the current UOW. ///</summary> /// <param name='nDepth'>[Not used.]</param> /// <exception cref="InvalidOperationException"> /// Thrown if not in the right state to do /// a rollback (in the data change phase of the UOW). /// </exception> public void Rollback(int nDepth) { if (this != m_uowService.ActiveUndoStack) { m_uowService.ActiveUndoStack.Rollback(nDepth); return; } if (m_uowService.CurrentProcessingState != UnitOfWorkService.FdoBusinessTransactionState.ProcessingDataChanges) { throw new InvalidOperationException("Rollback not supported in the current state."); } m_uowService.m_lock.ExitWriteLock(); m_currentBundle.Rollback(); m_currentBundle = null; RaisePropChangedCompleted(true); m_uowService.CurrentProcessingState = UnitOfWorkService.FdoBusinessTransactionState.ReadyForBeginTask; }
/// <summary> /// Two units of work have conflicting changes if they create, modify, or delete any of the same objects. /// In addition, since this is considered the change which must not be harmed by undoing (or redoing) /// </summary> internal bool AffectsSameObjects(FdoUnitOfWork other) { var itsChangedObjects = new HashSet <ICmObjectId>(other.m_deletedObjects); foreach (var item in other.m_dirtyObjects) { itsChangedObjects.Add(item.Id); } foreach (var item in other.m_newObjects) { itsChangedObjects.Add(item); } foreach (var item in m_dirtyObjects) { if (itsChangedObjects.Contains(item.Id)) { return(true); } } foreach (var item in m_newObjects) { if (itsChangedObjects.Contains(item)) { return(true); } } foreach (var item in m_deletedObjects) { if (itsChangedObjects.Contains(item)) { return(true); } } return(false); }
private void PushUowOnUndoStack(FdoUnitOfWork newUow) { m_undoBundles.Push(newUow); Debug.Assert(m_countUnsavedBundles >= 0); m_countUnsavedBundles++; }
private void EndUndoTaskCommon(bool updateDateModified) { if (m_uowService.CurrentProcessingState != UnitOfWorkService.FdoBusinessTransactionState.ProcessingDataChanges) { throw new InvalidOperationException("Cannot end task that has not been started."); } if (updateDateModified) { // A generic side effect of all changes is to update DateModified. // Collect the objects we want to record the modify time on. Don't do each as found: // Updating them will add new dirtballs, which will mess up the DirtyObjects iterator. // Also, we don't need the overhead of updating an object repeatedly if it has several changes. var collector = new HashSet <ICmObjectInternal>(); foreach (var item in m_currentBundle.DirtyObjects) { item.CollectDateModifiedObject(collector); } // Don't update the modify time on new objects, it should be near enough, and Undo will fail // trying to restore the modify time on the deleted object. var newObjects = m_currentBundle.NewObjects; foreach (var dmObj in collector) { if (!newObjects.Contains(dmObj.Id) && !m_currentBundle.IsDateModifiedExplicitly(dmObj)) { dmObj.UpdateDateModified(); } } // Update the project DateModified, but only once every 2 minutes at most. if (m_currentBundle.DirtyObjects.Count() > 0) { LangProject proj = m_currentBundle.DirtyObjects.ElementAt(0).Cache.LangProject as LangProject; TimeSpan span = new TimeSpan(DateTime.Now.Ticks - proj.DateModified.Ticks); if (span.Minutes >= 2 || m_currentBundle.DirtyObjects.Contains(proj)) { proj.UpdateDateModifiedInternal(); } } } m_uowService.m_lock.ExitWriteLock(); m_uowService.CurrentProcessingState = UnitOfWorkService.FdoBusinessTransactionState.BroadcastingPropChanges; if (m_currentBundle.HasDataChange) { // Can't redo these now. // If m_currentBundle can't be finished well, // then it can all be rolled back ClearRedoStack(); PushUowOnUndoStack(m_currentBundle); m_currentBundle.SetAfterXml(); m_uowService.SuppressSelections = true; try { // Handle Step 2.B (PropChanged calls) here. // Do them here because we may not commit yet. // 2.B can be moved after the optional save, but then rethink the states. m_uowService.SendPropChangedNotifications(m_currentBundle.GetPropChangeInformation(false)); } finally { m_uowService.SuppressSelections = false; } } m_currentBundle = null; m_uowService.CurrentProcessingState = UnitOfWorkService.FdoBusinessTransactionState.ReadyForBeginTask; // Do this after we are back in a safe state to do a new UOW, if necessary. RaisePropChangedCompleted(false); }