コード例 #1
0
        public void ResolveConflictLocalWins(SyncConflict conflict)
        {
            if (_closed)
            {
                throw new InvalidOperationException();
            }

            var tickCount       = _store.IncrementLocalRepilcaTickCount();
            var modifiedReplica = new ReplicaInfo
            {
                ReplicaId        = _store.GetLocalReplicaId(),
                ReplicaTickCount = tickCount
            };
            var resolvedStatus = conflict.LocalItemInfo.Deleted ? SyncStatus.Delete : SyncStatus.Update;
            var data           = JObject.Parse("{item:{itemRefs:[]}}");

            if (resolvedStatus != SyncStatus.Delete)
            {
                var builder = SyncUtil.JsonItemFromSyncableItemInfo(conflict.LocalItemInfo);
                _store.BuildItemData(conflict.LocalItemInfo, builder);
                data = new JObject {
                    { "item", builder }
                };
            }

            using (var connection = _syncSessionDbConnectionProvider.GetSyncSessionDbConnection(_localSessionId))
            {
                SessionDbHelper.ResolveItemWithData(connection, conflict.RemoteItemInfo, resolvedStatus, modifiedReplica,
                                                    data);
            }
        }
コード例 #2
0
        private async Task SaveSyncData(IDbConnection connection, ISyncableItemInfo remoteSyncableItemInfo,
                                        SyncStatus status)
        {
            var data = JObject.Parse("{item:{itemRefs:[]}}");

            if (!remoteSyncableItemInfo.Deleted)
            {
                var request = new JObject
                {
                    { "sessionID", _remoteSessionId },
                    { "item", SyncUtil.SyncableItemInfoToJson(remoteSyncableItemInfo) }
                };
                var response = await _transport.TransportAsync(SyncEndpoint.GetItemData, request);

                data = response;
            }

            SessionDbHelper.SaveItemData(connection, remoteSyncableItemInfo, status, data);

            var itemRefs = (JArray)data["item"]["itemRefs"];

            foreach (var item in itemRefs)
            {
                ISyncableItemInfo itemRefInfo      = SyncUtil.SyncableItemInfoFromJson(item);
                ISyncableItemInfo localItemRefInfo = _store.LocateCurrentItemInfo(itemRefInfo);
                if (localItemRefInfo != null && localItemRefInfo.Deleted)
                {
                    await SaveSyncData(connection, itemRefInfo, SyncStatus.MayBeNeeded);
                }
            }
        }
コード例 #3
0
        public SyncSession(ISyncableStore store, ISyncSessionDbConnectionProvider syncSessionDbConnectionProvider,
                           ISyncTransport transport)
        {
            if (store == null)
            {
                throw new ArgumentNullException("store");
            }
            if (syncSessionDbConnectionProvider == null)
            {
                throw new ArgumentNullException("syncSessionDbConnectionProvider");
            }
            if (transport == null)
            {
                throw new ArgumentNullException("transport");
            }

            PushMaxBatchCount = 500;
            PushMaxBatchSize  = 1024 * 1024;

            PullMaxBatchCount = 5000;
            PullMaxBatchSize  = 1024 * 1024 * 10;

            _store = store;
            _syncSessionDbConnectionProvider = syncSessionDbConnectionProvider;
            _transport      = transport;
            _localSessionId = Guid.NewGuid().ToString();
            _syncSessionDbConnectionProvider.SessionStart(_localSessionId);
            using (var connection = _syncSessionDbConnectionProvider.GetSyncSessionDbConnection(_localSessionId))
            {
                SessionDbHelper.CreateSessionDbTables(connection);
            }
        }
コード例 #4
0
        private async Task <int> SaveChangesBatch(IDbConnection connection, IList <IReplicaInfo> localKnowledge,
                                                  int startItem)
        {
            var request = new JObject
            {
                { "sessionID", _remoteSessionId },
                { "startItem", startItem },
                { "maxBatchCount", PullMaxBatchCount },
                { "maxBatchSize", PullMaxBatchSize }
            };

            JObject response = await _transport.TransportAsync(SyncEndpoint.GetItemDataBatch, request);

            var batch = (JArray)response["batch"];

            foreach (var item in batch)
            {
                ISyncableItemInfo remoteSyncableItemInfo = SyncUtil.SyncableItemInfoFromJson(item["item"]);
                var itemData = new JObject {
                    { "item", item["item"] }
                };

                var status = SyncStatus.MayBeNeeded;
                if (!SyncUtil.KnowledgeContains(localKnowledge, remoteSyncableItemInfo.Modified))
                {
                    ISyncableItemInfo localSyncableItemInfo = _store.LocateCurrentItemInfo(remoteSyncableItemInfo);
                    status = SyncUtil.CalculateSyncStatus(remoteSyncableItemInfo, localSyncableItemInfo,
                                                          _remoteKnowledge);
                }
                SessionDbHelper.SaveItemData(connection, remoteSyncableItemInfo, status, itemData);
                var itemRefs = (JArray)itemData["item"]["itemRefs"];
                foreach (var itemRef in itemRefs)
                {
                    ISyncableItemInfo itemRefInfo      = SyncUtil.SyncableItemInfoFromJson(itemRef);
                    ISyncableItemInfo localItemRefInfo = _store.LocateCurrentItemInfo(itemRefInfo);
                    if (localItemRefInfo != null && localItemRefInfo.Deleted)
                    {
                        await SaveSyncData(connection, itemRefInfo, SyncStatus.MayBeNeeded);
                    }
                }
            }
            return(batch.Count);
        }
コード例 #5
0
        public void ResolveConflictRemoteWins(SyncConflict conflict)
        {
            if (_closed)
            {
                throw new InvalidOperationException();
            }

            var tickCount       = _store.IncrementLocalRepilcaTickCount();
            var modifiedReplica = new ReplicaInfo
            {
                ReplicaId        = _store.GetLocalReplicaId(),
                ReplicaTickCount = tickCount
            };
            var resolvedStatus = conflict.RemoteItemInfo.Deleted ? SyncStatus.Delete : SyncStatus.Update;

            using (var connection = _syncSessionDbConnectionProvider.GetSyncSessionDbConnection(_localSessionId))
            {
                SessionDbHelper.ResolveItemNoData(connection, conflict.RemoteItemInfo, resolvedStatus, modifiedReplica);
            }
        }
コード例 #6
0
        public void ResolveConflictMerge(SyncConflict conflict, JObject itemData)
        {
            if (_closed)
            {
                throw new InvalidOperationException();
            }

            long         tickCount       = _store.IncrementLocalRepilcaTickCount();
            IReplicaInfo modifiedReplica = new ReplicaInfo
            {
                ReplicaId        = _store.GetLocalReplicaId(),
                ReplicaTickCount = tickCount
            };
            ISyncableItemInfo itemInfo = conflict.RemoteItemInfo;

            using (
                IDbConnection connection = _syncSessionDbConnectionProvider.GetSyncSessionDbConnection(_localSessionId))
            {
                SessionDbHelper.ResolveItemWithData(connection, itemInfo, SyncStatus.Update, modifiedReplica, itemData);
            }
        }
コード例 #7
0
        private IEnumerable <SyncConflict> CheckForDuplicates(IDbConnection connection)
        {
            var conflicts = new List <SyncConflict>();

            var changedItemInfos = _store.LocateChangedItems(_remoteKnowledge).ToList();

            foreach (string itemType in _store.GetItemTypes())
            {
                IDbCommand getInsertedItemsCommand = connection.CreateCommand();
                getInsertedItemsCommand.CommandText =
                    String.Format(
                        "SELECT ItemID, SyncStatus, ItemType, GlobalCreatedReplica, CreatedTickCount, GlobalModifiedReplica, ModifiedTickCount, ItemData  FROM SyncItems WHERE SyncStatus={0} AND ItemType='{1}'",
                        (int)SyncStatus.Insert, itemType);
                using (IDataReader reader = getInsertedItemsCommand.ExecuteReader())
                {
                    while (reader != null && reader.Read())
                    {
                        long              itemId              = Convert.ToInt64(reader["ItemID"]);
                        IReplicaInfo      createdReplicaInfo  = SessionDbHelper.ReplicaInfoFromDataReader(reader, "Created");
                        IReplicaInfo      modifiedReplicaInfo = SessionDbHelper.ReplicaInfoFromDataReader(reader, "Modified");
                        ISyncableItemInfo remoteItemInfo      = new SyncableItemInfo
                        {
                            ItemType = itemType,
                            Created  = createdReplicaInfo,
                            Modified = modifiedReplicaInfo,
                            Deleted  = false
                        };
                        var remoteItemData = JObject.Parse((string)reader["ItemData"]);

                        foreach (var changedItemInfo in changedItemInfos)
                        {
                            if (changedItemInfo.ItemType != remoteItemInfo.ItemType)
                            {
                                continue;
                            }
                            if (SyncUtil.KnowledgeContains(_remoteKnowledge, changedItemInfo.Created))
                            {
                                continue;
                            }

                            // Inserted here without remote knowledge, could be a dup
                            var builder = SyncUtil.JsonItemFromSyncableItemInfo(changedItemInfo);
                            _store.BuildItemData(changedItemInfo, builder);

                            var localItemData = new JObject {
                                { "item", builder }
                            };

                            DuplicateStatus dupStatus = _store.GetDuplicateStatus(remoteItemInfo.ItemType, localItemData,
                                                                                  remoteItemData);
                            if (dupStatus == DuplicateStatus.Exact)
                            {
                                SessionDbHelper.ReplaceAllItemRefs(connection, _store, remoteItemInfo, changedItemInfo);
                                long         tickCount       = _store.IncrementLocalRepilcaTickCount();
                                IReplicaInfo modifiedReplica = new ReplicaInfo
                                {
                                    ReplicaId        = _store.GetLocalReplicaId(),
                                    ReplicaTickCount = tickCount
                                };
                                SessionDbHelper.ResolveItemNoData(connection, remoteItemInfo,
                                                                  SyncStatus.DeleteNonExisting, modifiedReplica);
                                break;
                            }
                            if (dupStatus == DuplicateStatus.Possible)
                            {
                                // TODO: clean this up, this call does more than we need
                                SessionDbHelper.ResolveItemNoData(connection, remoteItemInfo, SyncStatus.InsertConflict,
                                                                  remoteItemInfo.Modified);
                                conflicts.Add(new SyncConflict(itemId, SyncStatus.InsertConflict, changedItemInfo,
                                                               remoteItemInfo));
                                break;
                            }
                        }
                    }
                }
            }
            return(conflicts);
        }
コード例 #8
0
        private IEnumerable <SyncConflict> LoadConflicts(IDbConnection connection)
        {
            var        conflicts = new List <SyncConflict>();
            IDbCommand getInsertedItemsCommand = connection.CreateCommand();

            getInsertedItemsCommand.CommandText =
                String.Format(
                    "SELECT ItemID, SyncStatus, ItemType, GlobalCreatedReplica, CreatedTickCount, GlobalModifiedReplica, ModifiedTickCount, ItemData  FROM SyncItems WHERE SyncStatus NOT IN ({0},{1},{2},{3},{4},{5})",
                    (int)SyncStatus.Insert,
                    (int)SyncStatus.Update,
                    (int)SyncStatus.Delete,
                    (int)SyncStatus.DeleteNonExisting,
                    (int)SyncStatus.MayBeNeeded,
                    (int)SyncStatus.InsertConflict
                    );
            using (var reader = getInsertedItemsCommand.ExecuteReader())
            {
                while (reader != null && reader.Read())
                {
                    var createdReplicaInfo  = SessionDbHelper.ReplicaInfoFromDataReader(reader, "Created");
                    var modifiedReplicaInfo = SessionDbHelper.ReplicaInfoFromDataReader(reader, "Modified");
                    var itemType            = (string)reader["ItemType"];
                    var remoteItemInfo      = new SyncableItemInfo
                    {
                        ItemType = itemType,
                        Created  = createdReplicaInfo,
                        Modified = modifiedReplicaInfo,
                        Deleted  = false
                    };
                    var itemId = Convert.ToInt64(reader["ItemID"]);
                    var status = (SyncStatus)reader["SyncStatus"];

                    ISyncableItemInfo localItemInfo = _store.LocateCurrentItemInfo(remoteItemInfo);

                    if (status == SyncStatus.UpdateConflict)
                    {
                        // Check to see if the "conflict" is actually an exact same update
                        var builder = SyncUtil.JsonItemFromSyncableItemInfo(localItemInfo);
                        _store.BuildItemData(localItemInfo, builder);

                        var localItemData = new JObject {
                            { "item", builder }
                        };

                        var remoteItemData = JObject.Parse((string)reader["ItemData"]);
                        var dupStatus      = _store.GetDuplicateStatus(remoteItemInfo.ItemType, localItemData,
                                                                       remoteItemData);
                        if (dupStatus == DuplicateStatus.Exact)
                        {
                            var tickCount       = _store.IncrementLocalRepilcaTickCount();
                            var modifiedReplica = new ReplicaInfo
                            {
                                ReplicaId        = _store.GetLocalReplicaId(),
                                ReplicaTickCount = tickCount
                            };
                            SessionDbHelper.ResolveItemNoData(connection, remoteItemInfo, SyncStatus.Update,
                                                              modifiedReplica);
                            // TODO: Really should have an update status that just updates the modified repos without doing everything else, but this should work
                            continue;
                        }
                    }

                    conflicts.Add(new SyncConflict(itemId, status, localItemInfo, remoteItemInfo));
                }
            }

            return(conflicts);
        }
コード例 #9
0
        private async Task <IEnumerable <SyncConflict> > PullChanges()
        {
            ReportProgressAndCheckCacellation(new SyncProgress
            {
                Stage           = SyncStage.FindingRemoteChanges,
                PercentComplete = 0,
                Message         = "Looking for remote changes"
            });

            var request = new JObject {
                { "sessionID", _remoteSessionId }
            };

            var localKnowledge = _store.GenerateLocalKnowledge().ToList();

            request.Add(new JProperty("knowledge", SyncUtil.KnowledgeToJson(localKnowledge)));

            JObject response = await _transport.TransportAsync(SyncEndpoint.GetChanges, request);

            _remoteKnowledge = SyncUtil.KnowledgeFromJson(response["knowledge"]);
            var totalChanges = (int)response["totalChanges"];

            ReportProgressAndCheckCacellation(new SyncProgress
            {
                Stage           = SyncStage.FindingRemoteChanges,
                PercentComplete = 100,
                Message         = String.Format("Found {0} remote changes", totalChanges)
            });

            ReportProgressAndCheckCacellation(new SyncProgress
            {
                Stage           = SyncStage.DownloadingRemoteChanges,
                PercentComplete = 0,
                Message         = String.Format("Downloading {0} remote changes", totalChanges)
            });
            using (var connection = _syncSessionDbConnectionProvider.GetSyncSessionDbConnection(_localSessionId))
            {
                connection.ExecuteNonQuery("BEGIN");
                SessionDbHelper.ClearSyncItems(connection);

                long startTick = Environment.TickCount;
                int  previousPercentComplete = -1;
                for (int i = 1; i <= totalChanges;)
                {
                    i += await SaveChangesBatch(connection, localKnowledge, i);

                    int percentComplete = ((i * 100) / totalChanges);
                    if (percentComplete != previousPercentComplete)
                    {
                        ReportProgressAndCheckCacellation(new SyncProgress
                        {
                            Stage           = SyncStage.DownloadingRemoteChanges,
                            PercentComplete = percentComplete,
                            Message         =
                                String.Format("Downloading remote changes, {0}% complete ({1})", percentComplete,
                                              String.Format("Averaging {0}ms/item over {1} items",
                                                            (Environment.TickCount - startTick) / i, i))
                        });
                    }
                    previousPercentComplete = percentComplete;
                }
                connection.ExecuteNonQuery("COMMIT");
                ReportProgressAndCheckCacellation(new SyncProgress
                {
                    Stage           = SyncStage.DownloadingRemoteChanges,
                    PercentComplete = 100,
                    Message         = String.Format("Downloaded all {0} remote changes", totalChanges)
                });

                ReportProgressAndCheckCacellation(new SyncProgress
                {
                    Stage           = SyncStage.CheckingForConflicts,
                    PercentComplete = 0,
                    Message         = "Looking for conflicts"
                });
                var conflicts = new List <SyncConflict>();
                conflicts.AddRange(CheckForDuplicates(connection));
                conflicts.AddRange(LoadConflicts(connection));
                ReportProgressAndCheckCacellation(new SyncProgress
                {
                    Stage           = SyncStage.CheckingForConflicts,
                    PercentComplete = 100,
                    Message         = String.Format("Found {0} conflicts", conflicts.Count)
                });
                return(conflicts);
            }
        }