/// <summary>
        /// Apply changes sent by a client to the server.
        /// </summary>
        /// <param name="serverBlob">Blob sent in the incoming request</param>
        /// <param name="entities">Changes from the client</param>
        /// <returns>Response containing the new knowledge and conflict/error information.</returns>
        public ApplyChangesResponse ApplyChanges(byte[] serverBlob, List<IOfflineEntity> entities)
        {
            WebUtil.CheckArgumentNull(serverBlob, "serverBlob");
            WebUtil.CheckArgumentNull(entities, "entities");
            
            if (0 == serverBlob.Length)
            {
                throw new InvalidOperationException("serverBlob is empty");
            }

            var syncBlob = new SyncBlob();

            SyncBlob incomingBlob = SyncBlob.DeSerialize(serverBlob);

            PopulateClientScopeNameAndSyncId(incomingBlob);
            
            // Set the scope name in the response blob.
            syncBlob.ClientScopeName = incomingBlob.ClientScopeName;
            
            // If the requested scope does not exists, then throw an error since we 
            // don't initialize scopes on upload requests.
            if (!CheckIfScopeExists())
            {
                throw SyncServiceException.CreateResourceNotFound("Scope does not exist");
            }

            byte[] clientKnowledgeBlob = incomingBlob.ClientKnowledge;

            // Initialize a SqlSyncProvider object.
            _sqlSyncProvider = CreateSqlSyncProviderInstance(_clientScopeName, _serverConnectionString, _configuration.SyncObjectSchema);

            var response = new ApplyChangesResponse();

            // Deserialize the knowledge or create new empty knowledge.
            SyncKnowledge clientKnowledge = GetSyncKnowledgeFromBlob(clientKnowledgeBlob);

            // If there are no entities to upload, then return the client knowledge as is.
            if (entities.Count == 0)
            {
                response.Conflicts = new List<SyncConflict>();
                response.Errors = new List<SyncError>();
                
                syncBlob.ClientKnowledge = clientKnowledge.Serialize();

                response.ServerBlob = syncBlob.Serialize();

                return response;
            }

            // Client never has any forgotten knowledge. So create a new one.
            var forgottenKnowledge = new ForgottenKnowledge(_sqlSyncProvider.IdFormats, clientKnowledge);

            // Convert the entities to dataset using the custom converter.
            DataSet changesDS = _converter.ConvertEntitiesToDataSet(entities);

            var stats = new SyncSessionStatistics();
            var sessionContext = new SyncSessionContext(_sqlSyncProvider.IdFormats, new SyncCallbacks());

            _sqlSyncProvider.BeginSession(SyncProviderPosition.Remote, sessionContext);

            ulong tickCount = 0;
            SyncKnowledge updatedClientKnowldege;

            try
            {
                uint batchSize;
                SyncKnowledge serverKnowledge;

                // This gives us the server knowledge.
                _sqlSyncProvider.GetSyncBatchParameters(out batchSize, out serverKnowledge);

                var changeBatch = new ChangeBatch(_sqlSyncProvider.IdFormats, clientKnowledge, forgottenKnowledge);
                changeBatch.SetLastBatch();

                //Note: There is a possiblity of (-ve) item exceptions , between two uploads from the 
                // same client (for example: in case of RI failures). This would result in an incorrect value if the function
                // FindMinTickCountForReplica is used to get the last tickcount. So, we need to ignore the -ve item exceptions 
                // when finding the tickcount for the client replica from the server knowledge.

                /* Logic:
                 * SyncKnowledge.GetKnowledgeForItemId could be used for itemid Zero and then we can find the mintickcount for client replica id.
                 * This does not however seem to work, so we use the KnowledgeInspector and enumerate over each ClockVector
                 * and find the client clockvector and get its tickcount.
                 * 
                 * Assumption: The above approach assumes that we don't have any positive exceptions in the knowledge.
                 */
                try
                {
                    // Check if the client replica key exists.
                    uint clientReplicaKey = serverKnowledge.ReplicaKeyMap.LookupReplicaKey(_clientSyncId);

                    var ki = new KnowledgeInspector(1, serverKnowledge);
                    var clockVector = (ClockVector)ki.ScopeClockVector;
                    int noOfReplicaKeys = clockVector.Count;

                    for (int i = noOfReplicaKeys - 1; i >= 0; i--)
                    {
                        if (clockVector[i].ReplicaKey == clientReplicaKey)
                        {
                            tickCount = clockVector[i].TickCount;
                            break;
                        }
                    }
                }
                catch (ReplicaNotFoundException exception)
                {
                    SyncTracer.Info("ReplicaNotFoundException. NEW CLIENT. Exception details: {0}",
                                    WebUtil.GetExceptionMessage(exception));
                    // If the knowedge does not contain the client replica (first apply), initialize tickcount to zero.
                    tickCount = 0;
                }

                // Increment the tickcount
                tickCount++;

                // update the made with knowledge to include the new tickcount.
                updatedClientKnowldege = new SyncKnowledge(_sqlSyncProvider.IdFormats, _clientSyncId, tickCount);
                updatedClientKnowldege.Combine(clientKnowledge);

                // The incoming data does not have metadata for each item, so we need to create it at this point.
                AddSyncColumnsToDataSet(changesDS, tickCount);

                // Make DbSyncContext
                var dbSyncContext = new DbSyncContext
                {
                    IsDataBatched = false,
                    IsLastBatch = true,
                    DataSet = changesDS,
                    MadeWithKnowledge = updatedClientKnowldege,
                    MadeWithForgottenKnowledge = forgottenKnowledge,
                    ScopeProgress = new DbSyncScopeProgress()
                };

                _conflicts = new List<SyncConflict>();
                _syncErrors = new List<SyncError>();

                // Subscribe to the ApplyChangeFailed event to handle conflicts.
                _sqlSyncProvider.ApplyChangeFailed += SqlSyncProviderApplyChangeFailed;

                // Subscribe to the ChangesApplied event to read the server tickcount incase there are any conflicts.
                _sqlSyncProvider.ChangesApplied += SqlSyncProviderChangesApplied;

                //NOTE: The ConflictResolutionPolicy pass into the method is IGNORED.
                // Conflicts can be logged by subscribing to the failed events
                _sqlSyncProvider.ProcessChangeBatch(Microsoft.Synchronization.ConflictResolutionPolicy.DestinationWins,
                                                   changeBatch,
                                                   dbSyncContext, new SyncCallbacks(), stats);

                if (0 != _conflicts.Count)
                {
                    _sqlSyncProvider.GetSyncBatchParameters(out batchSize, out serverKnowledge);

                    // The way the current P2P provider works, versions are bumped up when conflicts are resolved on the server.
                    // This would result in us sending the changes to the client on the next download request. We want
                    // to not enumerate that change again on the next request from the same client. 
                    // The solution is to get the server knowledge after all changes are applied and then
                    // project the knowledge of each conflictign item and add it as a positive exception to the updated client knowledge.

                    AddConflictItemsKnowledgeToClientKnowledge(updatedClientKnowldege, serverKnowledge);
                }
            }
            finally
            {
                _sqlSyncProvider.EndSession(sessionContext);
            }

            // Don't send any updates to the server knowledge since the client has not got any updates yet.
            // This updated knowledge will only include an update to the client tickcount.
            // The client would obtain the server knowledge when it does a get changes.
            // If we include the serverknowlege, the client would never get any items that are
            // between the current server knowledge and the client known server knowledge.

            syncBlob.ClientKnowledge = updatedClientKnowldege.Serialize();
            response.ServerBlob = syncBlob.Serialize();

            response.Conflicts = _conflicts;
            response.Errors = _syncErrors;

            return response;
        }
Example #2
0
        /// <summary>
        /// Возвращает пакет изменений, содержащий метаданные элементов, которые отсутствовали в указанном наборе знаний от поставщика назначения
        /// </summary>
        /// <param name="batchSize">Size of the batch.</param>
        /// <param name="destinationKnowledge">The destination knowledge.</param>
        /// <returns></returns>
        public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge)
        {
            ChangeBatch retVal    = null;
            ulong       tickCount = GetNextTickCount();


            List <ItemChange> changes = DetectChanges(destinationKnowledge, batchSize);

            retVal = new ChangeBatch(IdFormats, destinationKnowledge, Replica.ForgottenKnowledge);

            // Add the changes to the ChangeBatch with our made with knowledge
            // (Made width knowledge is the knowledge the other side will "learn" if they apply these
            // changes successfully)
            retVal.BeginUnorderedGroup();
            retVal.AddChanges(changes);
            // If last change batch, mark accordingly
            // (We always enumerate full batches, so if our batch is less than the batch size we
            // must be at the last batch. The second condition is spurious.)
            bool isLastBatch = false;

            if ((changes.Count < batchSize) || (changes.Count == 0))
            {
                retVal.SetLastBatch();
                isLastBatch = true;
            }

            retVal.EndUnorderedGroup(Replica.CurrentKnowledge, isLastBatch);

            return(retVal);
        }
Example #3
0
        public ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out ForgottenKnowledge forgottenKnowledge, out object changeDataRetriever)
        {
            // Increment the tick count
            GetNextTickCount();

            // Get local changes
            List <ItemChange> changes = DetectChanges(destinationKnowledge, batchSize);

            // Update the knowledge with an updated local tick count
            SyncKnowledge.SetLocalTickCount(tickCount);

            // Construct the ChangeBatch and return it
            ChangeBatch changeBatch = new ChangeBatch(IdFormats, destinationKnowledge, ForgottenKnowledge);

            changeBatch.BeginUnorderedGroup();
            changeBatch.AddChanges(changes);
            if (changes.Count < batchSize || changes.Count == 0)
            {
                changeBatch.SetLastBatch();
            }
            changeBatch.EndUnorderedGroup(SyncKnowledge, changeBatch.IsLastBatch);

            // Return the forgotten knowledge
            forgottenKnowledge = ForgottenKnowledge;

            changeDataRetriever = this;

            return(changeBatch);
        }
Example #4
0
        public ChangeBatch GetChangeBatch(string path, uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever)
        {
            folderPath = path;
            GetNextTickCount();

            List<ItemChange> changes = DetectChanges(destinationKnowledge, batchSize);

            myKnowledge.SetLocalTickCount(tickCount);

            ChangeBatch changeBatchBuilder = new ChangeBatch(IdFormats,destinationKnowledge,  myForgottenKnowledge);

            changeBatchBuilder.BeginUnorderedGroup();

            changeBatchBuilder.AddChanges(changes);

            changeBatchBuilder.EndUnorderedGroup(myKnowledge, true);

            if ((changes.Count < batchSize) || (changes.Count == 0))
            {
                changeBatchBuilder.SetLastBatch();
            }

            changeDataRetriever = this;

            return changeBatchBuilder;
        }
Example #5
0
        /// <summary>
        /// Возвращает пакет изменений, содержащий метаданные элементов, которые отсутствовали в указанном наборе знаний от поставщика назначения
        /// </summary>
        /// <param name="batchSize">Size of the batch.</param>
        /// <param name="destinationKnowledge">The destination knowledge.</param>
        /// <returns></returns>
        public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge)
        {
            ChangeBatch retVal = null;
            ulong tickCount = GetNextTickCount();

            List<ItemChange> changes = DetectChanges(destinationKnowledge, batchSize);

            retVal = new ChangeBatch(IdFormats, destinationKnowledge, Replica.ForgottenKnowledge);

            // Add the changes to the ChangeBatch with our made with knowledge
            // (Made width knowledge is the knowledge the other side will "learn" if they apply these
            // changes successfully)
            retVal.BeginUnorderedGroup();
            retVal.AddChanges(changes);
            // If last change batch, mark accordingly
            // (We always enumerate full batches, so if our batch is less than the batch size we
            // must be at the last batch. The second condition is spurious.)
            bool isLastBatch = false;
            if ((changes.Count < batchSize) || (changes.Count == 0))
            {
                retVal.SetLastBatch();
                isLastBatch = true;
            }

            retVal.EndUnorderedGroup(Replica.CurrentKnowledge, isLastBatch);

            return retVal;
        }