예제 #1
0
 private ConflictCallback ToOnGameThread(ConflictCallback conflictCallback)
 {
     return((resolver, original, originalData, unmerged, unmergedData) => {
         Logger.d("Invoking conflict callback");
         PlayGamesHelperObject.RunOnGameThread(() =>
                                               conflictCallback(resolver, original, originalData, unmerged, unmergedData));
     });
 }
예제 #2
0
        public void OpenWithAutomaticConflictResolution(string filename, DataSource source,
                                                        ConflictResolutionStrategy resolutionStrategy,
                                                        Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
        {
            Misc.CheckNotNull(filename);
            Misc.CheckNotNull(completedCallback);
            var prefetchDataOnConflict        = false;
            ConflictCallback conflictCallback = null;

            completedCallback = ToOnGameThread(completedCallback);

            if (conflictCallback == null)
            {
                conflictCallback = (resolver, original, originalData, unmerged, unmergedData) =>
                {
                    switch (resolutionStrategy)
                    {
                    case ConflictResolutionStrategy.UseOriginal:
                        resolver.ChooseMetadata(original);
                        return;

                    case ConflictResolutionStrategy.UseUnmerged:
                        resolver.ChooseMetadata(unmerged);
                        return;

                    case ConflictResolutionStrategy.UseLongestPlaytime:
                        if (original.TotalTimePlayed >= unmerged.TotalTimePlayed)
                        {
                            resolver.ChooseMetadata(original);
                        }
                        else
                        {
                            resolver.ChooseMetadata(unmerged);
                        }

                        return;

                    default:
                        Logger.e("Unhandled strategy " + resolutionStrategy);
                        completedCallback(SavedGameRequestStatus.InternalError, null);
                        return;
                    }
                }
            }
            ;

            conflictCallback = ToOnGameThread(conflictCallback);

            if (!IsValidFilename(filename))
            {
                Logger.e("Received invalid filename: " + filename);
                completedCallback(SavedGameRequestStatus.BadInputError, null);
                return;
            }

            InternalOpen(filename, source, resolutionStrategy, prefetchDataOnConflict, conflictCallback,
                         completedCallback);
        }
예제 #3
0
 private ConflictCallback ToOnGameThread(ConflictCallback conflictCallback)
 {
     return(delegate(IConflictResolver resolver, ISavedGameMetadata original, byte[] originalData, ISavedGameMetadata unmerged, byte[] unmergedData)
     {
         Logger.d("Invoking conflict callback");
         PlayGamesHelperObject.RunOnGameThread(delegate
         {
             conflictCallback(resolver, original, originalData, unmerged, unmergedData);
         });
     });
 }
예제 #4
0
        public void OpenWithManualConflictResolution(string filename, DataSource source, bool prefetchDataOnConflict,
                                                     ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
        {
            Misc.CheckNotNull(filename);
            Misc.CheckNotNull(conflictCallback);
            Misc.CheckNotNull(completedCallback);

            conflictCallback  = ToOnGameThread(conflictCallback);
            completedCallback = ToOnGameThread(completedCallback);

            if (!IsValidFilename(filename))
            {
                OurUtils.Logger.e("Received invalid filename: " + filename);
                completedCallback(SavedGameRequestStatus.BadInputError, null);
                return;
            }

            InternalOpen(filename, source, ConflictResolutionStrategy.UseManual, prefetchDataOnConflict,
                         conflictCallback, completedCallback);
        }
예제 #5
0
 void Open(ISavedGameClient savedGameClient, bool useAutomaticResolution, ConflictCallback conflictCallback,
           Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
 {
     if (useAutomaticResolution)
     {
         savedGameClient.OpenWithAutomaticConflictResolution(
             PlatformSaveUtil.remoteSaveFileName,
             DataSource.ReadNetworkOnly,
             ConflictResolutionStrategy.UseLongestPlaytime,
             completedCallback);
     }
     else
     {
         savedGameClient.OpenWithManualConflictResolution(
             PlatformSaveUtil.remoteSaveFileName,
             DataSource.ReadNetworkOnly,
             true,
             conflictCallback,
             completedCallback);
     }
 }
예제 #6
0
        /// <summary>
        /// Applies a given change set to provided db
        /// with possibility to limit which tables are changed via xFilter delegate,
        /// and custom conflict resolution via xConflict delegate
        /// See https://sqlite.org/session/sqlite3changeset_apply.html
        /// </summary>
        /// <param name="db">which db ("main" only) to apply changes to</param>
        /// <param name="changeSet">change set to apply to db</param>
        /// <param name="xFilter">use null to not filter, else delegate to limit tables changes applied to</param>
        /// <param name="xConflict">use null to ignore conflicts, else delegate to handle conflicts</param>
        /// <param name="ctx">context passed as first argument to xFilter and xConflict delegates</param>
        /// <returns></returns>
        public static Result ChangeSetApply(SQLite.SQLiteConnection db, SQLiteChangeSet changeSet, FilterCallback xFilter, ConflictCallback xConflict, object ctx)
        {
            Sqlite3DatabaseHandle dbHandle = db?.Handle ?? IntPtr.Zero;

            if (dbHandle == IntPtr.Zero)
            {
                return(Result.Misuse);
            }

            if (xConflict == null)
            {
                xConflict = new ConflictCallback(CallbackIgnoreConflicts);
            }

            // pinning not needed since just passing back thru; see https://blogs.msdn.microsoft.com/jmstall/2006/10/09/gchandle-tointptr-vs-gchandle-addrofpinnedobject/
            // Warning: if conflict handler is in unmanaged code then ctx should be pinned in Alloc and use AddrOfPinnedObject instead of ToIntPtr
            GCHandle gch    = GCHandle.Alloc(ctx);                                  // ok if ctx is null; pinning is unneeded since not kept past ChangeSetApply call
            IntPtr   pCtx   = (ctx == null) ? IntPtr.Zero : GCHandle.ToIntPtr(gch); // we don't pass GCHandle wrapper if null, instead pass NULL
            var      result = ChangeSetApply(dbHandle, changeSet.size, changeSet.buffer, xFilter, xConflict, pCtx);

            gch.Free();

            return(result);
        }
예제 #7
0
        private void InternalOpen(string filename, DataSource source, ConflictResolutionStrategy resolutionStrategy,
                                  bool prefetchDataOnConflict, ConflictCallback conflictCallback,
                                  Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
        {
            int conflictPolicy; // SnapshotsClient.java#RetentionPolicy

            switch (resolutionStrategy)
            {
            case ConflictResolutionStrategy.UseLastKnownGood:
                conflictPolicy = 2 /* RESOLUTION_POLICY_LAST_KNOWN_GOOD */;
                break;

            case ConflictResolutionStrategy.UseMostRecentlySaved:
                conflictPolicy = 3 /* RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED */;
                break;

            case ConflictResolutionStrategy.UseLongestPlaytime:
                conflictPolicy = 1 /* RESOLUTION_POLICY_LONGEST_PLAYTIME*/;
                break;

            case ConflictResolutionStrategy.UseManual:
                conflictPolicy = -1 /* RESOLUTION_POLICY_MANUAL */;
                break;

            default:
                conflictPolicy = 3 /* RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED */;
                break;
            }

            using (var task =
                       mSnapshotsClient.Call <AndroidJavaObject>("open", filename, /* createIfNotFound= */ true,
                                                                 conflictPolicy))
            {
                AndroidTaskUtils.AddOnSuccessListener <AndroidJavaObject>(
                    task,
                    dataOrConflict =>
                {
                    if (dataOrConflict.Call <bool>("isConflict"))
                    {
                        var conflict = dataOrConflict.Call <AndroidJavaObject>("getConflict");
                        AndroidSnapshotMetadata original =
                            new AndroidSnapshotMetadata(conflict.Call <AndroidJavaObject>("getSnapshot"));
                        AndroidSnapshotMetadata unmerged =
                            new AndroidSnapshotMetadata(
                                conflict.Call <AndroidJavaObject>("getConflictingSnapshot"));

                        // Instantiate the conflict resolver. Note that the retry callback closes over
                        // all the parameters we need to retry the open attempt. Once the conflict is
                        // resolved by invoking the appropriate resolution method on
                        // AndroidConflictResolver, the resolver will invoke this callback, which will
                        // result in this method being re-executed. This recursion will continue until
                        // all conflicts are resolved or an error occurs.
                        AndroidConflictResolver resolver = new AndroidConflictResolver(
                            this,
                            mSnapshotsClient,
                            conflict,
                            original,
                            unmerged,
                            completedCallback,
                            () => InternalOpen(filename, source, resolutionStrategy,
                                               prefetchDataOnConflict,
                                               conflictCallback, completedCallback));

                        var originalBytes = original.JavaContents.Call <byte[]>("readFully");
                        var unmergedBytes = unmerged.JavaContents.Call <byte[]>("readFully");
                        conflictCallback(resolver, original, originalBytes, unmerged, unmergedBytes);
                    }
                    else
                    {
                        using (var snapshot = dataOrConflict.Call <AndroidJavaObject>("getData"))
                        {
                            AndroidJavaObject metadata = snapshot.Call <AndroidJavaObject>("freeze");
                            completedCallback(SavedGameRequestStatus.Success,
                                              new AndroidSnapshotMetadata(metadata));
                        }
                    }
                });

                AddOnFailureListenerWithSignOut(
                    task,
                    exception => {
                    OurUtils.Logger.d("InternalOpen has failed: " + exception.Call <string>("toString"));
                    var status = mAndroidClient.IsAuthenticated() ?
                                 SavedGameRequestStatus.InternalError :
                                 SavedGameRequestStatus.AuthenticationError;
                    completedCallback(status, null);
                }
                    );
            }
        }
예제 #8
0
        public void ResolveConflicts(IEnumerable <TConflictItem> conflictItems, Func <TConflictItem, string> getItemKey,
                                     ConflictCallback <TConflictItem> foundConflict, bool commitWinner = true)
        {
            if (conflictItems == null)
            {
                return;
            }

            foreach (var conflictItem in conflictItems)
            {
                var itemKey = getItemKey(conflictItem);

                if (String.IsNullOrEmpty(itemKey))
                {
                    continue;
                }

                TConflictItem existingItem;

                if (_winningItemsByKey.TryGetValue(itemKey, out existingItem))
                {
                    // a conflict was found, determine the winner.
                    var winner = ResolveConflict(existingItem, conflictItem, logUnresolvedConflicts: false);

                    if (winner == null)
                    {
                        //  No winner.  Keep track of the conflictItem, so that if a subsequent
                        //  item wins for this key, both items (for which there was no winner when
                        //  compared to each other) can be counted as conflicts and removed from
                        //  the corresponding list.

                        List <TConflictItem> unresolvedConflictsForKey;
                        if (!_unresolvedConflictItems.TryGetValue(itemKey, out unresolvedConflictsForKey))
                        {
                            unresolvedConflictsForKey         = new List <TConflictItem>();
                            _unresolvedConflictItems[itemKey] = unresolvedConflictsForKey;

                            //  This is the first time we hit an unresolved conflict for this key, so
                            //  add the existing item to the unresolved conflicts list
                            unresolvedConflictsForKey.Add(existingItem);
                        }

                        //  Add the new item to the unresolved conflicts list
                        unresolvedConflictsForKey.Add(conflictItem);

                        continue;
                    }

                    TConflictItem loser = conflictItem;
                    if (!ReferenceEquals(winner, existingItem))
                    {
                        // replace existing item
                        if (commitWinner)
                        {
                            _winningItemsByKey[itemKey] = conflictItem;
                        }
                        else
                        {
                            _winningItemsByKey.Remove(itemKey);
                        }
                        loser = existingItem;
                    }

                    foundConflict(winner, loser);

                    //  If there were any other items that tied with the loser, report them as conflicts here
                    List <TConflictItem> previouslyUnresolvedConflicts;
                    if (_unresolvedConflictItems.TryGetValue(itemKey, out previouslyUnresolvedConflicts))
                    {
                        foreach (var previouslyUnresolvedItem in previouslyUnresolvedConflicts)
                        {
                            //  Don't re-report the item that just lost and was already reported
                            if (object.ReferenceEquals(previouslyUnresolvedItem, loser))
                            {
                                continue;
                            }

                            //  Call ResolveConflict with the new winner and item that previously had an unresolved
                            //  conflict, so that the correct message will be logged recording that the winner
                            //  won and why
                            ResolveConflict(winner, previouslyUnresolvedItem, logUnresolvedConflicts: true);

                            foundConflict(winner, previouslyUnresolvedItem);
                        }
                        _unresolvedConflictItems.Remove(itemKey);
                    }
                }
                else if (commitWinner)
                {
                    _winningItemsByKey[itemKey] = conflictItem;
                }
            }
        }
예제 #9
0
 public static Result ApplySessionChangeSet(this SQLite.SQLiteConnection db, SQLiteChangeSet changeSet, FilterCallback xFilter, ConflictCallback xConflict, object ctx)
 {
     return(ChangeSetApply(db, changeSet, xFilter, xConflict, ctx));
 }
예제 #10
0
 private static extern Result ChangeSetApply(Sqlite3DatabaseHandle db, int changeSetBufferSize, Sqlite3ChangesetBuffer changeSetBuffer,
                                             [MarshalAs(UnmanagedType.FunctionPtr)] FilterCallback xFilter,
                                             [MarshalAs(UnmanagedType.FunctionPtr)] ConflictCallback xConflict,
                                             IntPtr pCtx);
예제 #11
0
 public void OpenWithManualConflictResolution(string filename, DataSource source,
                                              bool prefetchDataOnConflict, ConflictCallback conflictCallback,
                                              Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
 {
     throw new NotImplementedException(mMessage);
 }
        private void InternalOpen(string filename, DataSource source, ConflictResolutionStrategy resolutionStrategy,
                                  bool prefetchDataOnConflict, ConflictCallback conflictCallback,
                                  Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
        {
            Types.SnapshotConflictPolicy policy;
            switch (resolutionStrategy)
            {
            case ConflictResolutionStrategy.UseLastKnownGood:
                policy = Types.SnapshotConflictPolicy.LAST_KNOWN_GOOD;
                break;

            case ConflictResolutionStrategy.UseMostRecentlySaved:
                policy = Types.SnapshotConflictPolicy.MOST_RECENTLY_MODIFIED;
                break;

            case ConflictResolutionStrategy.UseLongestPlaytime:
                policy = Types.SnapshotConflictPolicy.LONGEST_PLAYTIME;
                break;

            case ConflictResolutionStrategy.UseManual:
                policy = Types.SnapshotConflictPolicy.MANUAL;
                break;

            default:
                policy = Types.SnapshotConflictPolicy.MOST_RECENTLY_MODIFIED;
                break;
            }

            mSnapshotManager.Open(filename, AsDataSource(source), policy,
                                  response =>
            {
                if (!response.RequestSucceeded())
                {
                    completedCallback(AsRequestStatus(response.ResponseStatus()), null);
                }
                else if (response.ResponseStatus() == Status.SnapshotOpenStatus.VALID)
                {
                    completedCallback(SavedGameRequestStatus.Success, response.Data());
                }
                else if (response.ResponseStatus() ==
                         Status.SnapshotOpenStatus.VALID_WITH_CONFLICT)
                {
                    // If we get here, manual conflict resolution is required.
                    NativeSnapshotMetadata original = response.ConflictOriginal();
                    NativeSnapshotMetadata unmerged = response.ConflictUnmerged();

                    // Instantiate the conflict resolver. Note that the retry callback closes over
                    // all the parameters we need to retry the open attempt. Once the conflict is
                    // resolved by invoking the appropriate resolution method on
                    // NativeConflictResolver, the resolver will invoke this callback, which will
                    // result in this method being re-executed. This recursion will continue until
                    // all conflicts are resolved or an error occurs.
                    NativeConflictResolver resolver = new NativeConflictResolver(
                        mSnapshotManager,
                        response.ConflictId(),
                        original,
                        unmerged,
                        completedCallback,
                        () => InternalOpen(filename, source, resolutionStrategy,
                                           prefetchDataOnConflict,
                                           conflictCallback, completedCallback)
                        );

                    // If we don't have to pre-fetch the saved games' binary data, we can
                    // immediately invoke the conflict callback. Note that this callback is
                    // constructed to execute on the game thread in
                    // OpenWithManualConflictResolution.
                    if (!prefetchDataOnConflict)
                    {
                        conflictCallback(resolver, original, null, unmerged, null);
                        return;
                    }

                    // If we have to prefetch the data, we delegate invoking the conflict resolution
                    // callback to the joiner instance (once both callbacks resolve, the joiner will
                    // invoke the lambda that we declare here, using the fetched data).
                    Prefetcher joiner = new Prefetcher((originalData, unmergedData) =>
                                                       conflictCallback(resolver, original, originalData, unmerged, unmergedData),
                                                       completedCallback);

                    // Kick off the read calls.
                    mSnapshotManager.Read(original, joiner.OnOriginalDataRead);
                    mSnapshotManager.Read(unmerged, joiner.OnUnmergedDataRead);
                }
                else
                {
                    Logger.e("Unhandled response status");
                    completedCallback(SavedGameRequestStatus.InternalError, null);
                }
            });
        }
예제 #13
0
 private void InternalManualOpen(string filename, DataSource source, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
 {
     mSnapshotManager.Open(filename, AsDataSource(source), Types.SnapshotConflictPolicy.MANUAL, delegate(GooglePlayGames.Native.PInvoke.SnapshotManager.OpenResponse response)
     {
         if (!response.RequestSucceeded())
         {
             completedCallback(AsRequestStatus(response.ResponseStatus()), null);
         }
         else if (response.ResponseStatus() == CommonErrorStatus.SnapshotOpenStatus.VALID)
         {
             completedCallback(SavedGameRequestStatus.Success, response.Data());
         }
         else if (response.ResponseStatus() == CommonErrorStatus.SnapshotOpenStatus.VALID_WITH_CONFLICT)
         {
             NativeSnapshotMetadata original = response.ConflictOriginal();
             NativeSnapshotMetadata unmerged = response.ConflictUnmerged();
             NativeConflictResolver resolver = new NativeConflictResolver(mSnapshotManager, response.ConflictId(), original, unmerged, completedCallback, delegate
             {
                 InternalManualOpen(filename, source, prefetchDataOnConflict, conflictCallback, completedCallback);
             });
             if (!prefetchDataOnConflict)
             {
                 conflictCallback(resolver, original, null, unmerged, null);
             }
             else
             {
                 Prefetcher @object = new Prefetcher(delegate(byte[] originalData, byte[] unmergedData)
                 {
                     conflictCallback(resolver, original, originalData, unmerged, unmergedData);
                 }, completedCallback);
                 mSnapshotManager.Read(original, @object.OnOriginalDataRead);
                 mSnapshotManager.Read(unmerged, @object.OnUnmergedDataRead);
             }
         }
         else
         {
             Logger.e("Unhandled response status");
             completedCallback(SavedGameRequestStatus.InternalError, null);
         }
     });
 }
예제 #14
0
 public void OpenWithManualConflictResolution(string filename, DataSource source,
                                          bool prefetchDataOnConflict, ConflictCallback conflictCallback,
                                          Action<SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
 {
     throw new NotImplementedException(mMessage);
 }
예제 #15
0
 private void InternalManualOpen(string filename, GooglePlayGames.BasicApi.DataSource source, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
 {
     this.mSnapshotManager.Open(filename, NativeSavedGameClient.AsDataSource(source), Types.SnapshotConflictPolicy.MANUAL, (Action <GooglePlayGames.Native.PInvoke.SnapshotManager.OpenResponse>)(response =>
     {
         if (!response.RequestSucceeded())
         {
             completedCallback(NativeSavedGameClient.AsRequestStatus(response.ResponseStatus()), (ISavedGameMetadata)null);
         }
         else if (response.ResponseStatus() == CommonErrorStatus.SnapshotOpenStatus.VALID)
         {
             completedCallback(SavedGameRequestStatus.Success, (ISavedGameMetadata)response.Data());
         }
         else if (response.ResponseStatus() == CommonErrorStatus.SnapshotOpenStatus.VALID_WITH_CONFLICT)
         {
             // ISSUE: object of a compiler-generated type is created
             // ISSUE: variable of a compiler-generated type
             NativeSavedGameClient.\u003CInternalManualOpen\u003Ec__AnonStorey142.\u003CInternalManualOpen\u003Ec__AnonStorey143 openCAnonStorey143 = new NativeSavedGameClient.\u003CInternalManualOpen\u003Ec__AnonStorey142.\u003CInternalManualOpen\u003Ec__AnonStorey143();
             // ISSUE: reference to a compiler-generated field
             openCAnonStorey143.\u003C\u003Ef__ref\u0024322 = this;
             // ISSUE: reference to a compiler-generated field
             openCAnonStorey143.original = response.ConflictOriginal();
             // ISSUE: reference to a compiler-generated field
             openCAnonStorey143.unmerged = response.ConflictUnmerged();
             // ISSUE: reference to a compiler-generated field
             // ISSUE: reference to a compiler-generated field
             // ISSUE: reference to a compiler-generated field
             // ISSUE: reference to a compiler-generated method
             openCAnonStorey143.resolver = new NativeSavedGameClient.NativeConflictResolver(this.mSnapshotManager, response.ConflictId(), openCAnonStorey143.original, openCAnonStorey143.unmerged, completedCallback, new Action(openCAnonStorey143.\u003C\u003Em__6E));
             if (!prefetchDataOnConflict)
             {
                 // ISSUE: reference to a compiler-generated field
                 // ISSUE: reference to a compiler-generated field
                 // ISSUE: reference to a compiler-generated field
                 conflictCallback((IConflictResolver)openCAnonStorey143.resolver, (ISavedGameMetadata)openCAnonStorey143.original, (byte[])null, (ISavedGameMetadata)openCAnonStorey143.unmerged, (byte[])null);
             }
             else
             {
                 // ISSUE: reference to a compiler-generated method
                 NativeSavedGameClient.Prefetcher prefetcher = new NativeSavedGameClient.Prefetcher(new Action <byte[], byte[]>(openCAnonStorey143.\u003C\u003Em__6F), completedCallback);
                 // ISSUE: reference to a compiler-generated field
                 this.mSnapshotManager.Read(openCAnonStorey143.original, new Action <GooglePlayGames.Native.PInvoke.SnapshotManager.ReadResponse>(prefetcher.OnOriginalDataRead));
                 // ISSUE: reference to a compiler-generated field
                 this.mSnapshotManager.Read(openCAnonStorey143.unmerged, new Action <GooglePlayGames.Native.PInvoke.SnapshotManager.ReadResponse>(prefetcher.OnUnmergedDataRead));
             }
         }
         else
         {
             Logger.e("Unhandled response status");
             completedCallback(SavedGameRequestStatus.InternalError, (ISavedGameMetadata)null);
         }
     }));
 }
예제 #16
0
 public void OpenWithManualConflictResolution(string filename, GooglePlayGames.BasicApi.DataSource source, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback)
 {
     Misc.CheckNotNull <string>(filename);
     Misc.CheckNotNull <ConflictCallback>(conflictCallback);
     Misc.CheckNotNull <Action <SavedGameRequestStatus, ISavedGameMetadata> >(completedCallback);
     conflictCallback  = this.ToOnGameThread(conflictCallback);
     completedCallback = NativeSavedGameClient.ToOnGameThread <SavedGameRequestStatus, ISavedGameMetadata>(completedCallback);
     if (!NativeSavedGameClient.IsValidFilename(filename))
     {
         Logger.e("Received invalid filename: " + filename);
         completedCallback(SavedGameRequestStatus.BadInputError, (ISavedGameMetadata)null);
     }
     else
     {
         this.InternalManualOpen(filename, source, prefetchDataOnConflict, conflictCallback, completedCallback);
     }
 }